欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Java中的Map【九】WeakHashMap类

程序员文章站 2024-02-11 21:16:40
...

       所使用的jdk版本为1.8.0_172版本,先看一下 WeakHashMap<K,V> 在JDK中Map的UML类图中的主要继承实现关系:

Java中的Map【九】WeakHashMap类

概述

       WeakHashMap 是基于 弱引用(WeakReference)类型实现的。 在 WeakHashMap 中,对键K的引用是弱引用类型,当某个键不再正常使用,比如只被弱引用关联时,我们知道此时垃圾回收器会回收该键,此时WeakHashMap将自动移除该键对应的映射条目。null 值和 null 键都被支持。该类具有与 HashMap 类相似的性能特征,并具有相同的效能参数初始容量 和加载因子。WeakHashMap 的实现是不同步的,即线程不安全的。

示例如下:

public static void main(String[] args) {
        //weakHashMap存储 学生-分数 映射
        WeakHashMap<Student, Integer> weakHashMap = new WeakHashMap<>();
        //小明和小华对象分别有强引用关联:xiaoMing 和 xiaoHua;小亮直接new的对象,没有强引用关系
        Student xiaoMing = new Student("小明", 9);
        Student xiaoHua = new Student("小华",8);
        weakHashMap.put(xiaoMing, 100);
        weakHashMap.put(xiaoHua, 86);
        weakHashMap.put(new Student("小亮",9), 59);

        System.out.println("GC 前:");
        System.out.println(weakHashMap);

        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("GC 后:");
        System.out.println(weakHashMap);
    }

    public static class Student {
        private String name;
        private Integer age;

        public Student(String name, Integer age){
            this.name = name;
            this.age = age;
        }

        public String getName(){
            return this.name;
        }

        public Integer getAge(){
            return this.age;
        }

        @Override
        public int hashCode() {
            return name.hashCode() ^ age.hashCode();
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj) {
                return true;
            }
            if (obj instanceof Student) {
                return name.equals(((Student) obj).getName()) && age.equals(((Student) obj).getAge());
            }
            return false;
        }

        @Override
        public String toString() {
            return "{name:"+name+",age:"+age+"}";
        }
    }

程序运行结果:

GC 前:
{{name:小明,age:9}=100, {name:小华,age:8}=86, {name:小亮,age:9}=59}
GC 后:
{{name:小明,age:9}=100, {name:小华,age:8}=86}

       new 出的小亮学生对象,没有强引用关联,只有weakHashMap 对它的弱引用,当GC时,小亮对象会被回收,weakHashMap 会回收小亮对象键对应的映射关系。

数据结构      

public class WeakHashMap<K,V>
    extends AbstractMap<K,V>
    implements Map<K,V> {

      WeakHashMap 底层又其内部类节点数组Entry<K,V>[] table  和Entry<K,V> 链表实现的,另有队列ReferenceQueue queue 保存GC回收键的弱引用对象,用来清除映射。

      Entry<K,V> 类继承了WeakReference类,与其它Map实现最大的不同,就是把对键key的引用,由强引用关联改成了弱引用关联:

    private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
        V value;
        final int hash;
        Entry<K,V> next;

        /**
         * Creates new entry.
         */
        Entry(Object key, V value,
              ReferenceQueue<Object> queue,
              int hash, Entry<K,V> next) {
            //给键 Key 对象创建一个新的的弱引用关联,非强引用关联
            super(key, queue);
            this.value = value;
            this.hash  = hash;
            this.next  = next;
        }

实现原理

put(K key, V value) 方法

public V put(K key, V value) {
        //如果key是null的话,使用一个空Object对象代替
        Object k = maskNull(key);
        //扰动函数计算hash值
        int h = hash(k);
        //getTable()方法中清除过时的key(已被GC回收的key)对应的映射关系
        Entry<K,V>[] tab = getTable();
        int i = indexFor(h, tab.length);

        for (Entry<K,V> e = tab[i]; e != null; e = e.next) {
            if (h == e.hash && eq(k, e.get())) {
                V oldValue = e.value;
                if (value != oldValue)
                    e.value = value;
                return oldValue;
            }
        }

        modCount++;
        Entry<K,V> e = tab[i];
        tab[i] = new Entry<>(k, value, queue, h, e);
        if (++size >= threshold)
            //扩容操作
            resize(tab.length * 2);
        return null;
    }

getTable() 方法

private Entry<K,V>[] getTable() {
        // 清除过时的key对应的映射
        expungeStaleEntries();
        return table;
    }

expungeStaleEntries() 方法

  /**
     * Expunges stale entries from the table.
     * 删除被GC回收键对应的映射关系
     */
    private void expungeStaleEntries() {
        //从ReferenceQueue队列中获取被回收的键
        for (Object x; (x = queue.poll()) != null; ) {
            //同步实现
            synchronized (queue) {
                @SuppressWarnings("unchecked")
                    Entry<K,V> e = (Entry<K,V>) x;
                int i = indexFor(e.hash, table.length);

                Entry<K,V> prev = table[i];
                Entry<K,V> p = prev;
                //遍历链表结构,找到被回收的键x,单链表删除节点
                while (p != null) {
                    Entry<K,V> next = p.next;
                    if (p == e) {
                        if (prev == e)
                            table[i] = next;
                        else
                            prev.next = next;
                        // Must not null out e.next;
                        // stale entries may be in use by a HashIterator
                        // value引用设为null,帮助GC回收
                        e.value = null; // Help GC
                        size--;
                        break;
                    }
                    prev = p;
                    p = next;
                }
            }
        }
    }

  expungeStaleEntries() 方法在扩容resize()、get(Object key)、size()等方法中都有调用。

注意点:

WeakHashMap 中的每个键对象间接地存储为一个弱引用的指示对象。因此,不管是在映射内还是在映射之外,只有在垃圾回收器清除某个键的弱引用之后,该键才会自动移除。

实现注意事项:WeakHashMap 中的值对象由普通的强引用保持。因此应该小心谨慎,确保值对象不会直接或间接地强引用其自身的键,因为这会阻止键的丢弃。注意,值对象可以通过 WeakHashMap 本身间接引用其对应的键;这就是说,某个值对象可能强引用某个其他的键对象,而与该键对象相关联的值对象转而强引用第一个值对象的键。处理此问题的一种方法是,在插入前将值自身包装在 WeakReferences 中,如:m.put(key, new WeakReference(value)),然后,分别用 get 进行解包。

 

题外:

分析以下程序的输出结果是什么效果:

public static void test1() {
        String four = "four", five = "five";
        WeakHashMap<String,Integer> weakHashMap1 = new WeakHashMap<>();
        weakHashMap1.put(four, 4);
        weakHashMap1.put(five, 5);
        weakHashMap1.put("six", 6);

        WeakHashMap<String,Integer> weakHashMap2 = new WeakHashMap<>();
        weakHashMap2.put(four, 4);
        weakHashMap2.put(five, 5);
        weakHashMap2.put(new String("six"), 6);

        System.out.println("GC 前:");
        System.out.println(weakHashMap1);
        System.out.println(weakHashMap2);

        System.gc();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("GC 后:");
        System.out.println(weakHashMap1);
        System.out.println(weakHashMap2);
    }

程序运行结果:

GC 前:
{six=6, four=4, five=5}
{six=6, four=4, five=5}
GC 后:
{six=6, four=4, five=5}
{four=4, five=5}

 

相关标签: Java WeakHashMap