WeakHashMap
的工作原理与WeakReference
与ReferenceQueue
的耦合非常相似——对此没有任何消息。下面是一个简单的例子,说明它应该如何工作:
public class ReferenceQueuePlayground {
public static void main(String[] args) {
ReferenceQueue<Referent> q = new ReferenceQueue<>();
Referent ref = new Referent();
WeakReference<Referent> weak = new WeakReference<>(ref, q);
ref = null;
// wait for GC to reclaim Referent
while (weak.get() != null) {
System.gc();
}
// this might return null, because ReferenceQueue is notified asynchronously
// but I am assuming the happy path here
Reference<? extends Referent> reference = q.poll();
// this will be false
System.out.println(reference == null);
// this will be true
System.out.println(reference.get() == null);
}
@RequiredArgsConstructor
@Getter
static class Referent {
}
}
这正是WeakHashMap
的工作方式——当referent
被回收,reference
被放在ReferenceQueue
上时,它会得到通知。在随后的某个操作中,调用expungeStaleEntries
,它基本上会从该ReferenceQueue
中逐个获取元素并对其进行操作。
我的问题是:如果referent
现在不在了,它怎么能"对他们采取行动"?这毕竟是一个...HASHMap
,所以为了删除元素,它必须知道它是hashCode
。你怎么能知道现在已经不存在的东西的hashCode
?
有两种方法。第一个是线性搜索。
由于引用确实不存在,并且您无法在其上计算hashCode
,因此您可以在Map
中的所有条目中搜索具有==
的Reference
。在发布的示例中,您可以添加几行,如:
WeakReference<Referent> weak = new WeakReference<>(ref, q);
// <--- this
System.out.println(weak);
ref = null;
while (weak.get() != null) {
System.out.println("not yet");
System.gc();
}
Reference<? extends Referent> reference = q.poll();
// <---- and this
System.out.println(reference);
这两者将打印相同的东西,这是完全有意义的。因此,理论上,WeakHashMap
可以取其得到的reference
(实际上是Entry
(并遍历其内部阵列,直到找到匹配。
显然,这将是缓慢的。
第二种方法是WeakHashMap
实际采用的方法。当第一次创建Entry
时,它计算hashCode
并将其放入本地字段:
/**
* Creates new entry.
*/
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;
}
在这一点上,它知道Key
,因此它可以计算hashCode
。稍后调用expungeStaleEntries
时:
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);
它已经知道hashCode
,因为它是在此之前计算的。它不知道Key
,但也不需要它。
这将有助于找到这个条目所在的bucket,但要真正找到特定的条目,它只能在条目本身上使用==
。由于Key
已经消失,equals
是不可能的,但这并不重要。