弱引用之WeakHashMap的源码解析

1.Entry

  1. Entry本身是一个弱引用。弱引用WeakReference引用的对象即referent为Key。
  2. Value并非弱引用,而是强引用。
  3. Entry中的链表是为了解决hash冲突。
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
    V value;
    final int hash;
    Entry<K,V> next;

    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() {//返回referent
        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) {// 判断是否为同一个Entry对象的方式:key & value 都相等
        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);
    }
}

put

public V put(K key, V value) {
    Object k = maskNull(key);
    int h = hash(k);
    Entry<K,V>[] tab = getTable();// 执行 expungeStaleEntries
    int i = indexFor(h, tab.length);// 在 tab 中获取当前 key & value 对应的Entry对象
    for (Entry<K,V> e = tab[i]; e != null; e = e.next) {// 条件成立,说明存在hash冲突
        if (h == e.hash && eq(k, e.get())) {// 如果hash值一样,并且对象也相等,说明相同此时key完全一样,对其value进行覆盖
            V oldValue = e.value;
            if (value != oldValue)
                e.value = value;
            return oldValue;// 覆盖后返回旧值
        }
        // if条件不成立,则通过当前Entry即e中的next指针移动,遍历其链表上全部的Entry以此比较,直到next指针指向null,则退出循环
    }

    modCount++;
    Entry<K,V> e = tab[i];
    //如果上述for循环存在成立,此时表明hash存在冲突,但是整个链表内没有key & value 值跟当前key & value相等,此时将新生Entry添加到链表头部,其next指针指向e
    //如果上述for循环不成立,则表明hash不存在冲突,e为null
    tab[i] = new Entry<>(k, value, queue, h, e);
    if (++size >= threshold)
        resize(tab.length * 2);
    return null;// 不存在覆盖则返回null
}

tab:Entry类型的数组。

判断key相同的条件:

  1. key的hash必须相等。
  2. 对象引用指向同一个对象视为相同Key。
  3. 步骤1成立 & 步骤2不成立 的情况下,通过Object的equals方法再次比较【扩展点】。如果没有在引用类型的Key中重写equals方法则等同于步骤2。如果重写了equals方法则即使对象引用不同,也可以通过重写equals方法返回想要的结果,此处就是使用方可扩展的点。

综上,针对引用类型的key,如果两个key引用值相等则新旧key肯定对应同一个Entry,值必须覆盖处理。如果两个key引用值不相等也可以对应一个Entry,值覆盖处理。

private Entry<K,V>[] getTable() {
    expungeStaleEntries();
    return table;
}
private static boolean eq(Object x, Object y) {
    return x == y || x.equals(y);
}

❓为啥不使用虚引用呢?

  1. 首先对于Reference的子类虚引用PhantomReference来说重写了抽象类Reference之get方法,并且返回值直接返回Null。
  2. referent即虚引用指向的对象只能通过抽象类Reference之get方法获取。
  3. WeakHashMap需要频繁利用抽象类Reference之get方法获取Key,目的是解决hash冲突后值覆盖条件的判断。

expungeStaleEntries

根据弱引用特性,发生GC时referent必被回收。同时WeakReference即Entry将会被添加至引用队列queue中。在WeakHashMap使用场景下Entry对象由于一直存在强引用是不会被回收的,但是Entry中的value属性必须进行回收,所以需要切断属性value存在的强引用,具体实现过程如下所示:

为啥在释放value的前提是通过比较Entry的引用呢?因为此时referent指向的Key已经被GC处理了。

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);//得到单链表中的头结点下标
            Entry<K,V> prev = table[i];//得到单链表中的头结点
            Entry<K,V> p = prev;
            while (p != null) {
                Entry<K,V> next = p.next;
                if (p == e) {// 同一个Entry的前提下,将当前Entry的value置为null,方便GC,避免OOM
                    if (prev == e)// 如果头结点就是目标节点
                        table[i] = next;//移除掉当前节点
                    else
                        prev.next = next;//目标节点非头结点
                    e.value = null; // 辅助GC 
                    size--;
                    break;
                }
                prev = p;
                p = next;
            }
        }
    }
}

调用方:put、size、resize涉及expungeStaleEntries方法的调用。

你可能感兴趣的:(哈希算法,算法)