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

IdentityHashMap 深度解析:基於同一性(==)的高性能 Map 實現原理

     編輯:Java 綜合教學

前言

IdentityHashMap 是 Java 集合框架中一種特殊的 Map 實現:它通過 引用同一性(==) 而非 equals() 判斷鍵相等。這意味著兩個 equals() 返回 true 的對象,在 IdentityHashMap 中視為不同鍵。本文從初始化、存儲、查找、刪除四大核心流程,配合源碼與流程圖,深度剖析其獨特的線性探測數組存儲機制。

一、核心特徵速覽

特性IdentityHashMapHashMap
鍵比較方式key1 == key2(引用同一性)key1.equals(key2)(邏輯相等)
哈希計算System.identityHashCode(key)key.hashCode()
底層結構單一 Object[] 數組(鍵值交替存放)Node[] 數組 + 鏈表/紅黑樹
衝突解決線性探測鏈表/樹化
負載因子固定 2/3默認 0.75,可調
典型用途對象身份追蹤、序列化、圖算法節點標記通用鍵值存儲

二、初始化:容量對齊與雙倍數組

構造方法

public IdentityHashMap() {
    init(DEFAULT_CAPACITY); // 默認 32
}
public IdentityHashMap(int expectedMaxSize) {
    init(capacity(expectedMaxSize)); // 對齊到 2 的冪次
}
public IdentityHashMap(Map<? extends K, ? extends V> m) {
    this((int) ((1 + m.size()) * 1.1));
    putAll(m);
}

核心 init 邏輯

private void init(int initCapacity) {
    threshold = (initCapacity * 2) / 3; // 閾值 = 容量 * 2/3
    table = new Object[2 * initCapacity]; // 數組長度 = 2 * 容量(鍵值交替)
}

關鍵點:

  • capacity(expectedMaxSize) 將預期大小向上對齊到最近的 2 的冪次(如 10→16,100→128),保證模運算均勻分布。
  • 數組長度為 2 * capacity,因為 table[2*i] 存鍵,table[2*i+1] 存值,**鍵值緊湊相鄰**,利於 CPU 緩存行命中。
  • 閾值固定為容量的 2/3,達到閾值觸發擴容(數組長度翻倍)。

三、存儲:put(K key, V value) — 線性探測插入

public V put(K key, V value) {
    Object k = maskNull(key);           // null 鍵轉為特殊佔位對象
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);               // 計算初始索引:(hash & (len-1)) * 2
    Object item;
    while ((item = tab[i]) != null) {   // 線性探測:步長 2(跳過值位置)
        if (item == k) {                // 同一性比較:找到相同鍵
            V oldValue = (V) tab[i + 1];
            tab[i + 1] = value;
            return oldValue;
        }
        i = nextKeyIndex(i, len);       // i = (i + 2) % len
    }
    modCount++;
    tab[i] = k;                         // 空槽:存鍵
    tab[i + 1] = value;                 // 緊鄰存值
    if (++size >= threshold) resize(len);
    return null;
}

private static int nextKeyIndex(int i, int len) {
    return (i + 2 < len ? i + 2 : 0);   // 環狀數組,步長 2
}

流程圖解

IdentityHashMap put 流程圖

關鍵機制

  1. maskNull(key):同 EnumMap,用單例 NULL 佔位區分「鍵為 null」與「槽位為空」。
  2. hash(k, len):(System.identityHashCode(k) & (len/2 - 1)) * 2,結果為偶數,直接指向鍵槽位。
  3. 線性探測步長 2:每次檢查下一個鍵槽位(跳過值槽位),環狀繞回。
  4. 擴容保證終止:因擴容後數組長度翻倍,負載因子降至 1/3,探測必定能找到空槽或原鍵,避免死循環。

四、查找:get(Object key) — 同邏輯探測

public V get(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    while (true) {
        Object item = tab[i];
        if (item == k) return (V) tab[i + 1]; // 同一性命中
        if (item == null) return null;        // 遇到空槽,鍵不存在
        i = nextKeyIndex(i, len);
    }
}

邏輯與 put 探測一致:同一性匹配返回值,遇 null 槽位判定不存在。

五、刪除:remove(Object key) — 雙清理 + closeDeletion

public V remove(Object key) {
    Object k = maskNull(key);
    Object[] tab = table;
    int len = tab.length;
    int i = hash(k, len);
    while (true) {
        Object item = tab[i];
        if (item == k) {
            modCount++;
            size--;
            V oldValue = (V) tab[i + 1];
            tab[i + 1] = null;  // 清值
            tab[i] = null;      // 清鍵
            closeDeletion(i);   // 關鍵:修復探測鏈
            return oldValue;
        }
        if (item == null) return null;
        i = nextKeyIndex(i, len);
    }
}

closeDeletion(int deleteIndex) 核心作用

線性探測中,刪除會造成「空洞」,導致後續探測提前終止。**必須將後續連續的鍵值對向前遷移**,填補空洞,維持探測鏈完整性。源碼邏輯:

  1. 從 deleteIndex + 2 開始掃描
  2. 對每個非空鍵,計算其「理想索引」hash(key, len)
  3. 若理想索引在刪除位置「之前」(環狀意義上),說明該鍵曾經歷過探測跳躍,必須遷移至刪除空槽
  4. 遷移後繼續掃描,直到遇到空槽結束

六、與 HashMap 核心差異總結

  1. 負載因子固定 2/3,不可調,擴容閾值更激進。
  2. 鍵值同數組交替存放,無 Entry 對象開銷,內存極其緊湊。
  3. 衝突解決:線性探測,無鏈表/樹,極端情況下最壞 O(n) 但常數極小。
  4. 核心差異:== 判斷鍵相等,不調用 equals(),hashCode() 也用 System.identityHashCode()。

七、適用場景與避坑指南

✅ 適用場景

  • 對象身份追蹤:如序列化框架維护「對象→句柄」映射
  • 圖算法節點訪問標記:以節點對象為鍵,值為訪問狀態
  • 弱引用/軟引用鍵場景:引用語義要求同一性
  • 性能極度敏感、鍵實例固定且已知的熱點緩存

❌ 禁忌場景

  • 鍵為字符串、包裝類等「邏輯相等即同鍵」的業務場景(會導致同值不同引用被視為不同鍵)
  • 需要序列化傳輸的 Map(IdentityHashMap 實現了 Serializable 但語義隨 JVM 實例變化)
  • 作為普通業務 Map 替代 HashMap

總結

IdentityHashMap 是「以同一性換性能、以約束換簡潔」的典範:單一數組 + 線性探測 + == 比較,極致輕量。理解其 closeDeletion 修復探測鏈的精妙設計,能加深對開放地址法哈希表的掌握。**業務開發幾乎用不到它,但框架/中間件開發者必須懂它。**

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