抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

前言

WeakHashMapJava 集合框架中的一种特殊 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); // 调用 WeakReference 构造函数,key 是弱引用
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; // 容量调整为 2 的幂
this.loadFactor = loadFactor;
threshold = (int)(capacity * loadFactor); // 计算扩容阈值
table = new Entry[capacity]; // 初始化底层数组
// queue 在类中已初始化为 new ReferenceQueue<>()
}

// 从已有 Map 初始化的构造方法
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); // 处理 null 键
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; // Help GC
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 在每次操作(如 putget)时通过 getTable() 调用 expungeStaleEntries(),清理这些条目。

使用场景

  • 缓存:当键的生命周期由外部控制,且希望在键失效时自动清理缓存。
  • 对象元数据:关联对象与元数据,当对象被回收时,元数据也随之移除。

评论