前言 WeakHashMap
是 Java
集合框架中的一种特殊 Map
实现,位于 java.util
包中。它与普通的 HashMap
不同之处在于,它使用弱引用 (Weak Reference)来存储键(key),这使得键对象可以被垃圾回收器(Garbage Collector, GC)回收,从而在键没有其他强引用时自动移除对应的键值对。这在某些场景下非常有用,比如缓存实现。
WeakHashMap
继承自 AbstractMap
,并实现了 Map
接口。它的核心特性是:
键是弱引用 :当键对象没有强引用指向它时,GC 会回收该键,WeakHashMap
会自动删除对应的键值对。
线程不安全 :与 HashMap
一样,WeakHashMap
不是线程安全的。如果需要线程安全,可以使用 Collections.synchronizedMap()
或 ConcurrentHashMap
。
典型用途 :缓存、对象元数据管理等场景,避免内存泄漏。
源码分析 数据结构 1 2 3 4 5 6 7 8 9 public class WeakHashMap <K,V> extends AbstractMap <K,V> implements Map <K,V> { private static final int DEFAULT_INITIAL_CAPACITY = 16 ; private static final float DEFAULT_LOAD_FACTOR = 0.75f ; Entry<K,V>[] table; private int size; private float loadFactor; private int threshold; private final ReferenceQueue<Object> queue = new ReferenceQueue <>(); }
table
:存储键值对的数组,元素类型是 Entry<K,V>
。
queue
:一个 ReferenceQueue
,用于跟踪被 GC 回收的弱引用键。
默认初始容量为 16,负载因子为 0.75,与 HashMap
类似。
WeakHashMap
的键值对是通过 Entry
类存储的,它继承自 WeakReference
:
1 2 3 4 5 6 7 8 9 10 11 12 private static class Entry <K,V> extends WeakReference <Object> implements Map .Entry<K,V> { V value; int hash; Entry<K,V> next; 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; } }
Entry
继承 WeakReference
,键通过 super(key, queue)
被包装为弱引用。
当键被 GC 回收时,ReferenceQueue
会收到通知,WeakHashMap
会清理对应的条目。
构造方法 WeakHashMap
提供了多个构造方法,用于初始化实例。这些构造方法主要负责设置底层数组的初始容量、负载因子以及引用队列。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public WeakHashMap () { this (DEFAULT_INITIAL_CAPACITY, DEFAULT_LOAD_FACTOR); } public WeakHashMap (int initialCapacity) { this (initialCapacity, DEFAULT_LOAD_FACTOR); } public WeakHashMap (int initialCapacity, float loadFactor) { if (initialCapacity < 0 ) throw new IllegalArgumentException ("Illegal Initial Capacity: " + initialCapacity); if (loadFactor <= 0 || Float.isNaN(loadFactor)) throw new IllegalArgumentException ("Illegal Load Factor: " + loadFactor); int capacity = 1 ; while (capacity < initialCapacity) capacity <<= 1 ; this .loadFactor = loadFactor; threshold = (int )(capacity * loadFactor); table = new Entry [capacity]; } public WeakHashMap (Map<? extends K, ? extends V> m) { this (Math.max((int ) (m.size() / DEFAULT_LOAD_FACTOR) + 1 , DEFAULT_INITIAL_CAPACITY), DEFAULT_LOAD_FACTOR); putAll(m); }
核心方法 put(K key, V value)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public V put (K key, V value) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); for (Entry<K,V> e = tab[i]; e != null ; e = e.next) { if (h == e.hash && eq(k, e.get())) { V oldValue = e.value; if (value != oldValue) e.value = value; return oldValue; } } Entry<K,V> e = tab[i]; tab[i] = new Entry <>(k, value, queue, h, e); if (++size >= threshold) resize(2 * tab.length); return null ; }
maskNull(key)
:WeakHashMap
支持 null
键,会将其转换为一个内部常量。
getTable()
:在操作前清理被 GC 回收的键(调用 expungeStaleEntries()
)。
如果键已存在,更新值;否则创建新的 Entry
并插入。
expungeStaleEntries()
- 清理过期条目1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 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; e.value = null ; size--; break ; } prev = p; p = next; } } } }
queue.poll()
:从引用队列中获取被 GC 回收的键。
找到对应的 Entry
,从链表中移除,并减少 size
。
这是 WeakHashMap
自动清理的核心逻辑。
get(Object key) 1 2 3 4 5 6 7 8 9 10 11 12 13 public V get (Object key) { Object k = maskNull(key); int h = hash(k); Entry<K,V>[] tab = getTable(); int i = indexFor(h, tab.length); Entry<K,V> e = tab[i]; while (e != null ) { if (e.hash == h && eq(k, e.get())) return e.value; e = e.next; } return null ; }
后记 弱引用的工作原理
WeakReference
是 Java 的引用类型之一。当键只有弱引用(即没有强引用)时,GC 会回收它,并将其加入 ReferenceQueue
。
WeakHashMap
在每次操作(如 put
、get
)时通过 getTable()
调用 expungeStaleEntries()
,清理这些条目。
使用场景
缓存 :当键的生命周期由外部控制,且希望在键失效时自动清理缓存。
对象元数据 :关联对象与元数据,当对象被回收时,元数据也随之移除。