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 ----------------------------------------------产生上述输出结果的原因,我想大家应该已经明白了吧。