
一、前提說明本文討論的擴容特指HashMap 完成初始化之后因新增元素導致 size 超過閾值而觸發的擴容過程不包含第一次創建內部數組的情況。JDK 7 與 JDK 8 在這一時機的處理邏輯存在根本性差異背后的設計思想也大不相同。二、JDK 7先擴容再插入2.1 執行流程JDK 7 在插入新節點前會進行一個雙重判斷當前元素數量是否已達到擴容閾值size threshold待插入的新節點所在的桶是否已有元素即是否發生哈希沖突。只有兩個條件同時滿足時才會先執行擴容再將新節點插入擴容后的空數組。如果目標桶為空即使 size 已經達到閾值也不會立即擴容而是直接把新節點放進空桶。// JDK 7 插入邏輯簡化示意if (size threshold table[bucketIndex] ! null) {resize(); // 擴容bucketIndex indexFor(hash, table.length); // 重新計算下標}addEntry(...); // 插入新節點2.2 優點避免新節點重復遷移新節點不會先放入舊數組、再馬上參與數據遷移減少了不必要的搬運開銷。空桶場景下的內存節省當新節點落入空桶時暫不擴容可以在哈希沖突較少時延遲數組分配一定程度上節約內存。2.3 缺點擴容規則不夠統一是否觸發擴容不僅取決于size和threshold還與目標桶是否為空相關。兩個容量相同、元素數量相同的 HashMap可能僅僅因為新節點落入的桶不同一個觸發擴容而另一個不擴容。這導致擴容行為不易預測負載因子的語義也被弱化。插入流程復雜化提前擴容后由于數組長度改變需要重新計算新節點的存儲位置增加了插入路徑的復雜度。三、JDK 8先插入確認新增后再擴容3.1 執行流程JDK 8 將擴容判斷后置先完成桶內查找與插入可能是鏈表追加或紅黑樹插入若本次插入確實是一個新鍵非覆蓋舊值則size加 1若插入后的size threshold觸發擴容。// JDK 8 插入邏輯簡化示意NodeK,V e ...; // 在桶中完成查找/插入if (e null) { // 新增鍵size;if (size threshold)resize();}afterNodeInsertion(evict); // 回調3.2 優點1. 擴容條件更清晰、統一是否擴容完全由size threshold決定不再依賴新節點是否落入空桶。負載因子成為真正意義上的“容量飽和度”指標語義明確行為可預測。2. 僅新增鍵時觸發擴容如果調用put的 key 已存在僅僅覆蓋舊值size保持不變自然也不會引發擴容避免了無意義的數組重建。3. 與紅黑樹機制無縫配合JDK 8 引入了“鏈表轉紅黑樹”的優化。采用“先完成桶內部操作再統一處理樹化、size 更新和擴容判斷”的流程代碼結構更加一致邏輯內聚便于維護。4. 擴容遷移代價降低使后置擴容可行JDK 8 的遷移算法做了關鍵優化因為數組容量按 2 倍擴展節點在新數組中的下標只有兩種可能——保持原下標或原下標 舊容量。只需判斷節點哈希值中與oldCap對應的那一位即可將原鏈表拆分為“高位鏈”和“低位鏈”無需重新計算完整哈希下標。因此即使新節點剛剛插入舊數組就立即參與一次遷移額外成本也遠低于 JDK 7這使得“先插入后擴容”的設計在性能上完全可接受。// JDK 8 擴容拆分示意NodeK,V loHead null, loTail null;NodeK,V hiHead null, hiTail null;for (NodeK,V e oldTab[j]; e ! null; e e.next) {if ((e.hash oldCap) 0) {// 保留在原下標} else {// 移動到 原下標 oldCap}}3.3 缺點臨界場景下的重復遷移新節點可能剛剛放入舊數組就因為擴容被再次遷移發生一次“無用搬運”。不再因空桶而延遲擴容JDK 8 丟棄了“目標桶為空則不擴容”的優化只要 size 超過閾值就會立即擴容可能比 JDK 7 更早分配更大的數組在內存敏感的場景下略顯激進。四、設計取舍總結JDK 7 與 JDK 8 在擴容時機上的差異本質上是一組設計取舍JDK 7 偏向局部優化盡量避免新節點的重復遷移同時在沖突較少時通過延遲擴容來節省內存。代價是擴容條件復雜化、行為不一致代碼邏輯耦合度高。JDK 8 追求全局統一與可維護性用一次可能的額外遷移換取了擴容規則的純粹與統一負載因子語義的嚴格保證與紅黑樹機制的和諧共生更清晰的代碼結構與更可預測的性能表現因此不能簡單地說 JDK 8 的“先插入再擴容”就一定更快。更準確的理解是正是因為 JDK 8 優化了遷移算法2 倍擴容下標二選一并引入了紅黑樹等新結構“先插入、確認 size 增加后再統一擴容”才成為更合適的設計選擇。這也是 JDK 在不斷演進中根據內部機制的變化對同一問題給出的不同最優解。五、對比一覽維度JDK 7JDK 8擴容時機插入前size≥閾值且桶非空插入后新增鍵且 size閾值擴容規則依賴哈希沖突情況不夠統一純粹依賴 size 與閾值統一清晰新節點遷移避免重復遷移可能剛插入就遷移一次空桶行為可延遲擴容節省內存不再延遲size 超閾值必擴容遷移算法重新計算所有節點下標只需拆分高位/低位鏈表代碼復雜度插入路徑分支多邏輯統一樹化與擴容分離配合機制純鏈表鏈表 紅黑樹理解這些差異有助于我們在不同的應用場景中更合理地評估 HashMap 的性能表現同時也體現了 JDK 設計團隊在性能、語義與可維護性之間的精妙平衡。