Java集合类学习之WeakHashMap

Posted by W-M on November 25, 2017

本篇随笔主要记录了我阅读WeakHashMap(JDK1.8)源码期间的对于WeakHashMap的一些实现上的个人理解,用于个人备忘,有不对的地方,请指出。


前言

顾名思义,WeakHashMap与HashMap的不同之处在于其key值是被弱引用包裹的,可以在一些情况下用于防止内存泄漏。关于WeakHashMap的应用场景,可以参考:用弱引用堵住内存泄漏。本文主要介绍WeakHashMap的内部实现机制。

不同于HashMap的数组+链表+红黑树的数据结构,WeakHashMap对于HashMap的数据结构进行了简化,使用的是数组+链表,并没有使用到红黑树,其基本节点实现如下:


Entry<K,V>[] table;

/**
 * The entries in this hash table extend WeakReference, using its main ref
 * field as the 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,就相当于创建了一个WeakReference,并将其关联到一个引用队列中
     */
    Entry(Object key, V value,
          ReferenceQueue<Object> queue,
          int hash, Entry<K,V> next) {
        super(key, queue);
        this.value = value;
        this.hash  = hash;
        this.next  = next;
    }

    @SuppressWarnings("unchecked")
    public K getKey() {
        return (K) WeakHashMap.unmaskNull(get());
    }

    public V getValue() {
        return value;
    }

    public V setValue(V newValue) {
        V oldValue = value;
        value = newValue;
        return oldValue;
    }

    public boolean equals(Object o) {
        if (!(o instanceof Map.Entry))
            return false;
        Map.Entry<?,?> e = (Map.Entry<?,?>)o;
        K k1 = getKey();
        Object k2 = e.getKey();
        if (k1 == k2 || (k1 != null && k1.equals(k2))) {
            V v1 = getValue();
            Object v2 = e.getValue();
            if (v1 == v2 || (v1 != null && v1.equals(v2)))
                return true;
        }
        return false;
    }

    public int hashCode() {
        K k = getKey();
        V v = getValue();
        return Objects.hashCode(k) ^ Objects.hashCode(v);
    }

    public String toString() {
        return getKey() + "=" + getValue();
    }
}

Put操作实现

private final ReferenceQueue<Object> queue = new ReferenceQueue<>();

public V put(K key, V value) {
    Object k = maskNull(key);
    int h = hash(k); // 计算其hash值
    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++;
    // 不存在则添加,注意新添加的所有节点都关联到了同一个Reference Queue中
    Entry<K,V> e = tab[i];
    tab[i] = new Entry<>(k, value, queue, h, e);
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;
}

至于WeakHashMap的get操作,remove操作等都是基于数组+链表进行的操作,逻辑都比较简单,这里就不赘述了。

弱引用的删除

在上面put方法中可以看到,WeakHashMap中的每个key都会关联到一个相同的ReferenceQueue中,当key被检测到是弱引用可达时,就会由Reference类中开启的后台线程将此key加入到此ReferenceQueue中,那么这个key的弱引用及其对应的value是如何从WeakHashMap中删除的呢?

原来WeakHashMap 有一个名为 expungeStaleEntries() 的私有方法,大多数 WeakHashMap 操作中会调用它(比如size()方法),并没有采用后台线程轮询此ReferenceQueue检测是否为空来实现。expungeStaleEntries()方法中去掉引用队列中所有失效的引用,并删除关联的映射。下面代码展示了 expungeStaleEntries() 的实现:

/**
 * Expunges stale entries from the table.
 */
private void expungeStaleEntries() {
    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); // 找到要删除的引用在map中的位置

            Entry<K,V> prev = table[i];
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {
                    if (prev == e) // 如果table[i]就是要删除的引用节点
                        table[i] = next;
                    else
                        prev.next = next; // 删除此引用
                    // Must not null out e.next;
                    // stale entries may be in use by a HashIterator
                    e.value = null; // Help GC // 删除此引用对应的值
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

关于WeakHashMap的源码就分析到这里。

(完)