一、概述
WeakHashMap 继承于AbstractMap,实现了Map接口。
WeakHashMap也是一个散列表,它存储的内容是键值对(key-value)映射,而且键和值都可以为null。与其他散列表不同的是,WeakHashMap的键是弱键。即在WeakHashMap中当某个键不再正常使用时,会被从WeakHashMap中自动移除。更精确的说,对于给定的一个键,其映射的存在并不影响垃圾回收器对该键的回收,这就使得该键是可终止的。被终止,然后被回收。当一个键被终止时,它对应的键值对也就从映射中移除了。
二、数据结构
拉链法,和HashMap类似(链表模式),只是key保存方式不一样,是WeakReference,使用时需要先判断key是否存在。
另外包含一个queue保存已被GC清除”的“弱引用的键”。
private final ReferenceQueue
ReferenceQueue
Reference queues, to which registered reference objects are appended by the garbage collector after the appropriate reachability changes are detected.
使用方式:
ReferenceQueue queue = new ReferenceQueue();
WeakReference ObjectRefer = new WeakReference(object, queue);
当object被回收后,queue中就会添加这个ObjectRefer。
Entry
继承 WeakReference
Entry(Object key, V value,
ReferenceQueue
三、特点
支持键、值为null
-
键为“弱键”(值还是强引用)
弱键是指什么?是指在WeakHashMap中的key的引用并不影响key被垃圾回收器回收。当key被回收时,WeakHashMap中的键值对也将被移除。
非线程安全。可使用Collections.synchronizedMap
Iterator是fast-fail的
四、要点实现
1.弱键实现原理
通过WeakReference和ReferenceQueue实现。
WeakHashMap的key是“弱键”,是WeakReference类型的;ReferenceQueue是一个队列,它会保存被GC回收的“弱键”。
- 新建WeakHashMap,将“键值对”添加到WeakHashMap中。 实际上,WeakHashMap是通过数组table保存Entry(键值对);每一个Entry实际上是一个单向链表,即Entry是键值对链表。
- 当某“弱键”不再被其它对象引用,并被GC回收时。在GC回收该“弱键”时,这个“弱键”也同时会被添加到ReferenceQueue(queue)队列中。
- 当下一次我们需要操作WeakHashMap时,会先同步table和queue。table中保存了全部的键值对,而queue中保存被GC回收的键值对;同步它们,就是删除table中被GC回收的键值对。
使用ReferenceQueue
在初始化entry时将key使用WeakReference保存,并设置回调队列为类中ReferenceQueue
Entry(Object key, V value,
ReferenceQueue
清除已被终止的key的键值队映射
private void expungeStaleEntries() {
for (Object x; (x = queue.poll()) != null; ) {
synchronized (queue) {
@SuppressWarnings("unchecked")
Entry e = (Entry) x;
int i = indexFor(e.hash, table.length);
Entry prev = table[i];
Entry p = prev;
while (p != null) {
Entry 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
e.value = null; // Help GC
size--;
break;
}
prev = p;
p = next;
}
}
}
}
2.弱键对基本方法实现的影响
resize时,由于弱键的存在可能会发生空间浪费。
在resize之后,判断当前size是否大于threshold/2,当小于时放弃这次扩容操作,依然使用之前的数组,并进行一次清除操作。
所有key相关方法
使用key.get()获取真实key进行操作,同时考虑key已经被回收导致的key==null情况。
clear操作
在原有操作基础上先清除queue。
public void clear() {
// clear out ref queue. We don't need to expunge entries
// since table is getting cleared.
while (queue.poll() != null)
;
modCount++;
Arrays.fill(table, null);
size = 0;
// Allocation of array may have caused GC, which may have caused
// additional entries to go stale. Removing these entries from the
// reference queue will make them eligible for reclamation.
while (queue.poll() != null)
;
}
支持key为null
使用一个NULL_OBJECT表示为null值。避免key被回收后获取到null与原有null键冲突。
3.被回收数据清除时机
对数据进行操作和查询之前会先进行清除操作。
4.HashIterator
实现了Iterator接口
其中使用了两个Object保存key值
nextKey, 添加强引用,避免在调用hasNext()和next()方法之间key被回收掉。在hasNext()方法中更新nextKey。
currentKey,添加强引用,保证next()需要使用这个key时不为null,在next()方法中更新。
private int index;
private Entry entry = null;
private Entry lastReturned = null;
private int expectedModCount = modCount;
/**
* Strong reference needed to avoid disappearance of key
* between hasNext and next
*/
private Object nextKey = null;
/**
* Strong reference needed to avoid disappearance of key
* between nextEntry() and any use of the entry
*/
private Object currentKey = null;
so,使用Iterator遍历Map时不会出现key被回收,返回错误数据的情况。保证遍历到的数据都是可用状态。