HashMap 是 Java 面試與日常開發最高頻的集合類。本文從數據結構、存取流程、哈希算法、擴容機制、性能參數、Fail-Fast 機制六大維度,結合 JDK 8 源碼(註:文中源碼為 JDK 7 風格,原理一致)深度剖析其內部運作,助你徹底掌握這把「鑰匙」。
transient Entry<K,V>[] table 是底層數組,每個槽位是一個 Entry 鏈表頭節點。
static class Entry<K,V> implements Map.Entry<K,V> {
final K key;
V value;
Entry<K,V> next; // 指向下一個節點,形成鏈表
final int hash; // 緩存 key 的 hash 值
// ...
}
JDK 8 引入紅黑樹:當鏈表長度 ≥ 8 且數組長度 ≥ 64 時轉為樹,查詢複雜度從 O(n) 降為 O(log n)。
key == null → 處理為 putForNullKey,固定放索引 0。hash = hash(key.hashCode())(二次哈希,見下節)。index = indexFor(hash, table.length) 定位槽位。e.hash == hash && (e.key == key || key.equals(e.key)) → 覆蓋值,返回舊值。addEntry(hash, key, value, index) 頭插法插入新節點。threshold → resize(2 * table.length) 擴容。static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
將高位信息混合到低位,減少哈希碰撞,尤其針對 hashCode() 只有低位變化的 key(如 Integer)。
static int indexFor(int h, int length) {
return h & (length - 1); // length 必須是 2 的 n 次冪
}
為什麼 length 必須是 2 的冪?
length - 1 二進制全為 1,h & (length-1) 等價於 h % length,但位運算極快。length-1 末位為 0,導致最後一位永遠為 0,浪費一半槽位,碰撞率飆升。size >= threshold = capacity * loadFactor(默認 0.75)。e.hash & (newCap-1)),遷移數據——極其消耗 CPU。initialCapacity 避免多次擴容。| 構造器 | 說明 |
|---|---|
HashMap() | 容量 16,負載因子 0.75 |
HashMap(int initialCapacity) | 指定容量,負載因子 0.75 |
HashMap(int initialCapacity, float loadFactor) | 完全自定義 |
modCount 記錄結構性修改次數(put、remove、clear)。expectedModCount = modCount。modCount != expectedModCount → 拋出 ConcurrentModificationException。ConcurrentHashMap 或 Collections.synchronizedMap。put(key, value) │ ├─ key==null → table[0] │ ├─ hash(key.hashCode()) → 二次哈希 │ ├─ indexFor(hash, length) → h & (n-1) │ ├─ 遍歷鏈表/紅黑樹 │ ├─ key 相等 → 覆蓋 value │ └─ 無匹配 → 頭插法新增 Entry │ └─ size++ ≥ threshold → resize(2×) → rehash 所有節點
new HashMap<>(預估尺寸 / 0.75f + 1)。hashCode() 與 equals(),保證一致性契約。entrySet() 而非 keySet() + get()。