首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java集合深度解析:WeakHashMap 弱鍵機制、源碼剖析與實戰應用

     編輯:Java 相關知識

在 Java 集合框架中,WeakHashMap 是一個極具特色的實現類。它繼承自 AbstractMap 並實現 Map 接口,底層同樣採用「拉鏈法」解決哈希衝突,但核心差異在於其鍵是「弱鍵」——當鍵不再被外部強引用時,垃圾回收器(GC)會自動回收該鍵,並將對應的鍵值對從映射中移除。這種特性讓它成為實現緩存、監聽器註冊表等場景的理想選擇,能有效避免內存洩漏。

一、核心原理:弱引用與引用隊列的協作

WeakHashMap 的「弱鍵」機制建立在 java.lang.ref.WeakReference 與 ReferenceQueue 之上:

  1. 存儲階段:調用 put(key, value) 時,鍵被包裝為 WeakReference,並註冊到隊列 queue 中。底層數組 table 存儲的是 Entry 節點,每個 Entry 繼承自 WeakReference<K>,形成單向鏈表。
  2. 回收觸發:當某鍵在外部不再被強引用,GC 掃描到該弱引用時,會將其回收,並將該 WeakReference 對象放入 ReferenceQueue。
  3. 同步清理:下一次對 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;
        }
    }
}

二、構造函數與核心 API

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() 確保表內無失效條目。

三、源碼關鍵點深度解析

1. Null 鍵的特殊處理

由於 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);
}

2. 獲取表數組前必清理

getTable()、size()、get()、put() 等方法均先調用 expungeStaleEntries(),保證返回的數據是「當前有效」的快照。

3. 擴容機制

當 size >= threshold 時觸發 resize(newCapacity),新容量為舊容量 2 倍(不超過 MAXIMUM_CAPACITY = 1 << 30)。擴容過程中會再次清理失效條目,若清理後元素數量低於新閾值一半,則放棄擴容並復用舊表,避免不必要的內存分配。

4. 迭代器與 Fast-Fail

迭代器繼承自抽象類 HashIterator,通過 expectedModCount 實現快速失效機制。迭代過程中會強引用當前鍵(currentKey、nextKey),防止迭代期間被 GC 回收導致 NoSuchElementException。

四、遍歷方式

1. 遍歷鍵值對(推薦,效率最高)

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();
    // 業務邏輯
}

2. 遍歷鍵

for (String key : map.keySet()) {
    Integer value = map.get(key);
}

3. 遍歷值

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 時機而定
    }
}

最佳實踐提醒:

  • 僅作為緩存/註冊表:不適合作為長期存儲,因為條目可能隨時消失。
  • 鍵必須可被 GC 回收:若鍵被常量池、靜態字段等強引用持有,弱引用機制將失效。
  • 非線程安全:多線程環境下請使用 Collections.synchronizedMap(new WeakHashMap<>()) 或改用 ConcurrentHashMap 搭配弱引用值。

六、總結

WeakHashMap 通過 WeakReference + ReferenceQueue 實現「鍵隨對象生命週期自動消亡」的語義,是 Java 中極少數與 GC 深度協作的集合類。掌握其 expungeStaleEntries() 清理時機、NULL_KEY 替換邏輯、擴容時的二次清理策略,有助於在緩存、監聽器管理、元數據映射等場景寫出更健壯、無內存洩漏的代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved