WeakHashMap是一种特殊的HashMap,那它特殊在哪里呢?WeakHashMap的键是弱引用对象,弱引用是实现WeakHashMap的关键所在,WeakHashMap特别适用于需要缓存的场景,当一个键对象被垃圾回收器回收时,那么相应的值对象的引用会从Map中删除。WeakHashMap能够节约存储空间,可用来缓存那些非必须存在的数据。
由于WeakHashMap的键值是弱引用对象,所以,即使我们没有向WeakHashMap中添加或者删除任意一个对象,对WeakHashMap的两次操作也可能会返回不一样的值,如
- 调用两次size()方法返回不同的值;
- 两次调用containsKey()方法,第一次返回true,第二次返回false,尽管两次使用的是同一个key;
WeakHashMap源码详解
WeakHashMap继承了Map接口,它的整体结构类似于HashMap,存在默认初始容量、最大容量以及负载因子等参数:
private static final int DEFAULT_INITIAL_CAPACITY = 16;
private static final int MAXIMUM_CAPACITY = 1 << 30;
private static final float DEFAULT_LOAD_FACTOR = 0.75f;
我们这里重点关注的是WeakHashMap中的键值对是如何被Java虚拟机自动回收的,先来看一下WeakHashMap存储键值对的数据结构:
Entry<K,V>[] table;
Entry类的声明为:
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>
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;
}
WeakHashMap的哈希冲突是采用拉链法实现的,每一个Entry本质上是一个单向链表,我们重点看方法体的第一行代码:
super(key, queue);
这一段代码其实是将原始对象包装成为了WeakReference对象,即将key包装为WeakReference对象,第二个参数queue是虚引用的引用队列,queue是WeakHashMap的成员变量,定义如下:
private final ReferenceQueue<Object> queue = new ReferenceQueue<>();
当被WeakReference包装的键值对象被垃圾回收器回收之后,对应的Entry对象会被添加到queue引用队列中,然后,WeakHashMap会通过queue来清理key对应的value,体现在源码当中就是expungeStaleEntries()方法,对WeakHashMap进行操作之前,比如调用get(Object key)、put(K key, V value)、size()或是扩容操作都会直接或间接调用该方法:
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) {
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;
}
}
}
}
该方法的主要作用就是遍历引用队列queue中的Entry对象,将key对应的value置为null,然后在Java虚拟机的下一次垃圾回收时,若value对象没有了其他强引用,则value对象就会被回收,这也就实现了通过垃圾回收器自动回收键值对的功能。
示例
最后,我们来看一个WeakHashMap的代码示例:
public class ReferenceTest2 {
private static Object key1, key2, key3;
public static void main(String[] args) throws InterruptedException {
WeakHashMap<Object, Integer> weakHashMap = getWeakHashMap();
printWeakHashMapInfo(weakHashMap);
// 由于weakHashMap的所有键值都有一个强引用与之关联,所以weakHashMap的所有键值对都不会被回收
System.gc();
Thread.sleep(2000);
printWeakHashMapInfo(weakHashMap);
// 将其中一个键值对应的强引用置为null,让Java虚拟机来进行自动回收
key1 = null;
System.gc();
Thread.sleep(2000);
printWeakHashMapInfo(weakHashMap);
}
private static WeakHashMap<Object, Integer> getWeakHashMap() {
WeakHashMap<Object, Integer> weakHashMap = new WeakHashMap<>();
key1 = new Object();
key2 = new Object();
key3 = new Object();
weakHashMap.put(key1, 1);
weakHashMap.put(key2, 1);
weakHashMap.put(key3, 1);
return weakHashMap;
}
private static void printWeakHashMapInfo(WeakHashMap<Object, Integer> weakHashMap) {
System.out.println("weakHashMap.size() = " + weakHashMap.size());
for (Object key : weakHashMap.keySet()) {
System.out.println("key: " + key + " value: " + weakHashMap.get(key));
}
System.out.println("----------------------------------------------");
}
}
运行结果为:
weakHashMap.size() = 3
key: java.lang.Object@70dea4e value: 1
key: java.lang.Object@7852e922 value: 1
key: java.lang.Object@4e25154f value: 1
----------------------------------------------
weakHashMap.size() = 3
key: java.lang.Object@70dea4e value: 1
key: java.lang.Object@7852e922 value: 1
key: java.lang.Object@4e25154f value: 1
----------------------------------------------
weakHashMap.size() = 2
key: java.lang.Object@70dea4e value: 1
key: java.lang.Object@4e25154f value: 1
----------------------------------------------
产生上述输出结果的原因,我想这里就不用再解释了吧。