1.Entry
- Entry本身是一个弱引用。弱引用WeakReference引用的对象即referent为Key。
- Value并非弱引用,而是强引用。
- 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() {//返回referentreturn (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();// 执行 expungeStaleEntriesint 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为nulltab[i] = new Entry<>(k, value, queue, h, e);if (++size >= threshold)resize(tab.length * 2);return null;// 不存在覆盖则返回null
}
tab:Entry类型的数组。
判断key相同的条件:
- key的hash必须相等。
- 对象引用指向同一个对象视为相同Key。
- 步骤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);
}
❓为啥不使用虚引用呢?
- 首先对于Reference的子类虚引用PhantomReference来说重写了抽象类Reference之get方法,并且返回值直接返回Null。
- referent即虚引用指向的对象只能通过抽象类Reference之get方法获取。
- 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,避免OOMif (prev == e)// 如果头结点就是目标节点table[i] = next;//移除掉当前节点elseprev.next = next;//目标节点非头结点e.value = null; // 辅助GC size--;break;}prev = p;p = next;}}}
}
调用方:put、size、resize涉及expungeStaleEntries方法的调用。