HashMap 是 Java 開發者最常使用的集合類之一,它提供了鍵值對的高效存儲與檢索。然而,許多開發者僅停留在「會用」層面,對其內部運作機制知之甚少。本文將深入剖析 java.util.HashMap 的源碼實現(涵蓋 Java 7 與 Java 8),探討其數據結構、擴容機制、線程安全問題、鍵的不可變性要求,以及 Java 8 引入的紅黑樹優化,並分析記憶體佔用與性能調優要點。
HashMap 實現了 Map<K, V> 接口,內部靜態類 Entry<K, V> 封裝鍵值對,包含以下字段:
final K key:鍵V value:值Entry<K, V> next:指向下一個 Entry 的引用,形成單向鏈表int hash:鍵的哈希值緩存,避免重複計算數據存儲在一個 Entry<K, V>[] 數組(稱為桶或槽)中,默認初始容量為 16。所有擁有相同哈希值(經過再哈希與取模運算後索引相同)的鍵值對會被放入同一個桶的鏈表中。
當調用 put() 或 get() 時,經過三步確定桶索引:
hashCode()。// Java 7
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
// Java 8 簡化為
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
h & (length - 1) 計算索引,要求數組長度必須是 2 的冪次,這樣 length - 1 的二進制全為 1,可均勻分佈索引。為了防止鏈表過長導致操作退化為 O(n),HashMap 會在元素數量超過閾值(capacity * loadFactor,默認 16 × 0.75 = 12)時自動擴容:
get/put/remove 效率。注意:擴容是耗時操作,預估數據量大時應通過構造函數指定 initialCapacity 避免頻繁擴容。
HashMap 非線程安全。多線程並發 put 可能導致:
get() 無限循環。解決方案:
Hashtable:所有方法同步,性能低下。Collections.synchronizedMap():同步包裝器,同樣鎖住整個 Map。ConcurrentHashMap (Java 5+):分段鎖(Java 7)或 CAS + synchronized + 紅黑樹(Java 8),僅鎖定桶級別,高並發下吞吐量遠超前兩者。若鍵對象在放入 Map 後被修改導致 hashCode() 改變,Map 將無法定位到原桶,造成數據「丟失」。
最佳實踐:使用 String、Integer 等不可變類作為鍵;自定義類作為鍵時,務必保證不可變,並正確重寫 equals() 和 hashCode()。
Java 8 將 Entry 重命名為 Node,並在桶節點數超過閾值(默認 8)時將鏈表轉換為紅黑樹(TreeNode),退化閾值為 6。紅黑樹屬於自平衡二叉搜索樹,查詢/插入/刪除複雜度從 O(n) 降為 O(log n),極大改善哈希碰撞嚴重時的性能。
// 簡化結構
static class Node<K,V> implements Map.Entry<K,V> {
final int hash; final K key; V value; Node<K,V> next;
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {
TreeNode<K,V> parent, left, right, prev; boolean red;
}
| 版本 | 節點類型 | 近似記憶體公式 (bytes) |
|---|---|---|
| Java 7 | Entry | 16 * N + 4 * CAPACITY |
| Java 8 | Node (純鏈表) | 16 * N + 4 * CAPACITY (同 Java 7) |
| Java 8 | TreeNode (全紅黑樹) | 44 * N + 4 * CAPACITY |
其中 N 為元素數量,CAPACITY 為數組容量。Java 7 採用延遲加載,首次 put() 才分配數組內存。
測試表明:200 萬數據,差的哈希函數(所有鍵返回常數)耗時 45 分鐘以上;優質哈希函數(如 return i;)僅需 2 秒。String、Integer 自帶優質哈希實現。
大數據場景下,構造時指定 new HashMap<>(預估大小 / 0.75f + 1) 可消除擴容重建數組與 rehash 的耗時。
改用 ConcurrentHashMap 或加鎖包裝。
理解 HashMap 內部原理不僅是面試必考點,更是編寫高性能 Java 代碼的基礎。掌握「數組+鏈表/紅黑樹」結構、擴容觸發條件、線程安全陷阱、鍵不可變要求、Java 8 樹化優化及內存模型,能幫助開發者在海量數據、高並發場景下做出正確的技術選型與調優決策。