在 Java 集合框架中,WeakHashMap 是一個極具特色的實現類。它繼承自 AbstractMap 並實現 Map 接口,底層同樣採用「拉鏈法」解決哈希衝突,但核心差異在於其鍵是「弱鍵」——當鍵不再被外部強引用時,垃圾回收器(GC)會自動回收該鍵,並將對應的鍵值對從映射中移除。這種特性讓它成為實現緩存、監聽器註冊表等場景的理想選擇,能有效避免內存洩漏。
WeakHashMap 的「弱鍵」機制建立在 java.lang.ref.WeakReference 與 ReferenceQueue 之上:
put(key, value) 時,鍵被包裝為 WeakReference,並註冊到隊列 queue 中。底層數組 table 存儲的是 Entry 節點,每個 Entry 繼承自 WeakReference<K>,形成單向鏈表。WeakReference 對象放入 ReferenceQueue。WeakHashMap 進行任意操作(如 get、put、size)時,會調用 expungeStaleEntries() 遍歷隊列,將已失效的鍵值對從 table 中剔除。關鍵方法 expungeStaleEntries() 源碼邏輯如下:
private void expungeStaleEntries() {
Entry<K,V> e;
while ((e = (Entry<K,V>) queue.poll()) != null) {
int h = e.hash;
int i = indexFor(h, 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.next = null; // Help GC
e.value = null; // "
size--;
break;
}
prev = p;
p = next;
}
}
}
WeakHashMap 提供四個構造函數,默認初始容量 16(2 的冪),默認加載因子 0.75f:
WeakHashMap()WeakHashMap(int initialCapacity)WeakHashMap(int initialCapacity, float loadFactor)WeakHashMap(Map<? extends K, ? extends V> m)常用 API 與 HashMap 大致一致:put、get、remove、size、clear、containsKey、keySet、values、entrySet 等。值得注意的是,所有公開方法在操作前都會先調用 expungeStaleEntries() 確保表內無失效條目。
由於 WeakReference 允許 null 鍵,若直接存入會被 GC 誤回收。WeakHashMap 內部定義靜態常量 NULL_KEY,通過 maskNull() / unmaskNull() 進行替換與還原:
private static final Object NULL_KEY = new Object();
private static Object maskNull(Object key) {
return (key == null ? NULL_KEY : key);
}
private static <K> K unmaskNull(Object key) {
return (K) (key == NULL_KEY ? null : key);
}
getTable()、size()、get()、put() 等方法均先調用 expungeStaleEntries(),保證返回的數據是「當前有效」的快照。
當 size >= threshold 時觸發 resize(newCapacity),新容量為舊容量 2 倍(不超過 MAXIMUM_CAPACITY = 1 << 30)。擴容過程中會再次清理失效條目,若清理後元素數量低於新閾值一半,則放棄擴容並復用舊表,避免不必要的內存分配。
迭代器繼承自抽象類 HashIterator,通過 expectedModCount 實現快速失效機制。迭代過程中會強引用當前鍵(currentKey、nextKey),防止迭代期間被 GC 回收導致 NoSuchElementException。
Iterator<Map.Entry<String, Integer>> iter = map.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry<String, Integer> entry = iter.next();
String key = entry.getKey();
Integer value = entry.getValue();
// 業務邏輯
}
for (String key : map.keySet()) {
Integer value = map.get(key);
}
for (Integer value : map.values()) {
// 僅需值時使用
}
import java.util.Map;
import java.util.WeakHashMap;
public class WeakHashMapCacheDemo {
public static void main(String[] args) throws InterruptedException {
// 模擬緩存鍵:只有緩存本身持有引用
String key1 = new String("user:1001");
String key2 = new String("user:1002");
Map<String, String> cache = new WeakHashMap<>();
cache.put(key1, "用戶資料-A");
cache.put(key2, "用戶資料-B");
System.out.println("初始大小: " + cache.size()); // 2
// 釋放外部強引用
key1 = null;
key2 = null;
// 強制 GC(僅演示,生產環境不可靠)
System.gc();
Thread.sleep(100); // 等待 Finalizer 線程處理 ReferenceQueue
// 下一次操作會觸發清理
System.out.println("GC 後大小: " + cache.size()); // 0 或 1,視 GC 時機而定
}
}
最佳實踐提醒:
Collections.synchronizedMap(new WeakHashMap<>()) 或改用 ConcurrentHashMap 搭配弱引用值。WeakHashMap 通過 WeakReference + ReferenceQueue 實現「鍵隨對象生命週期自動消亡」的語義,是 Java 中極少數與 GC 深度協作的集合類。掌握其 expungeStaleEntries() 清理時機、NULL_KEY 替換邏輯、擴容時的二次清理策略,有助於在緩存、監聽器管理、元數據映射等場景寫出更健壯、無內存洩漏的代碼。