Java集合之WeakHashMap详解

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

猜你喜欢

转载自blog.csdn.net/qq_38293564/article/details/80462506