【集合】JDK1.8 HashMap 底層數據結構深度解析

一、核心數據結構:為什么是 "數組 + 鏈表 + 紅黑樹"??

HashMap 的底層設計本質是用空間換時間,通過哈希表的快速定位特性,結合鏈表和紅黑樹處理沖突,平衡查詢與插入效率。?

1.1 基礎容器:哈希桶數組(Node [] table)?

  • 數組角色:作為哈希表的 "骨架",每個下標對應一個哈希桶(bucket),通過哈希值直接定位元素所在桶的位置,實現 O (1) 級別的快速訪問。?
  • Node 節點結構:數組中存儲的是Node<K,V>對象,包含四個核心字段:
static class Node<K,V> implements Map.Entry<K,V> {final int hash;    // 鍵的哈希值(經過擾動處理)final K key;       // 鍵(唯一)V value;           // 值Node<K,V> next;    // 下一個節點引用(用于鏈表)
}

?

這里的next字段是實現鏈表的關鍵,當多個元素哈希沖突時,會通過該字段串成鏈表。?

1.2 解決沖突:鏈表的作用?

當兩個不同的 key 計算出相同的哈希值(即哈希沖突)時,HashMap 會將新元素以鏈表節點的形式 "掛" 在同一哈希桶的尾部。這種處理方式稱為鏈地址法,是哈希表解決沖突的經典方案。?

1.3 性能優化:紅黑樹的引入?

JDK1.8 之前,HashMap 僅用數組 + 鏈表的結構,當哈希沖突嚴重時(如大量元素集中在同一桶中),鏈表會退化為 "長鏈",此時查詢時間復雜度會從 O (1) 退化到 O (n)。?

為解決這個問題,JDK1.8 引入了紅黑樹(TreeNode):當鏈表長度超過閾值(默認 8)且數組長度≥64 時,鏈表會自動轉為紅黑樹,將查詢時間復雜度優化到 O (log n)。?

二、關鍵機制解析:哈希、擴容與樹化?

2.1 哈希函數:如何計算元素在數組中的位置??

HashMap 的高效查詢依賴于哈希值的精準計算,核心步驟分為兩步:?

① 計算 key 的哈希值(擾動函數)

static final int hash(Object key) {int h;// 1. 取key的hashCode值;2. 高16位與低16位異或(擾動處理)return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
  • 為什么要做擾動處理??

若直接使用key.hashCode()的低幾位計算索引,當哈希值的高位變化大但低位變化小時,容易導致哈希沖突(例如hashCode=0x0001FFFF和0x0002FFFF,低 16 位相同)。通過高 16 位與低 16 位異或,能讓高位信息參與哈希計算,減少沖突概率。?

② 計算數組索引

// n為數組長度(必須是2的冪)
int index = (n - 1) & hash;
  • 這里用(n-1) & hash代替取模運算(hash % n),因為當 n 是 2 的冪時,兩者結果等價,但位運算效率更高。?

2.2 擴容機制:數組不夠用了怎么辦??

當 HashMap 中的元素數量超過閾值(threshold = 容量 × 負載因子)時,會觸發擴容(resize),將數組長度翻倍(新容量 = 舊容量 ×2)。?

擴容的核心步驟:?

????????(1)計算新容量與新閾值:默認初始容量為 16,負載因子 0.75,初始閾值 = 16×0.75=12。擴容后新容量 = 32,新閾值 = 32×0.75=24。?

????????(2)創建新數組:長度為新容量。?

????????(3)遷移舊元素:將舊數組中的元素重新計算索引,遷移到新數組中(JDK1.8 優化點:通過hash & oldCap判斷元素是否需要移動,避免重復計算哈希)。?

擴容時的元素遷移邏輯:?

  • 若元素所在桶是單節點:直接計算新索引并放入新數組。?
  • 若元素所在桶是鏈表:?

通過hash & oldCap將鏈表拆分為兩個子鏈表:?

  • 結果為 0:元素留在原索引位置(新數組的j處)。?
  • 結果為非 0:元素遷移到新索引位置(新數組的j + oldCap處)。?
  • 若元素所在桶是紅黑樹:拆分樹節點為兩個子樹,若子樹長度≤6 則退化為鏈表。?

2.3 樹化與反樹化:鏈表和紅黑樹如何轉換??

樹化(鏈表轉紅黑樹)的觸發條件:?

  1. 鏈表長度≥8(TREEIFY_THRESHOLD = 8)。?
  1. 數組長度≥64(MIN_TREEIFY_CAPACITY = 64)。?

若數組長度不足 64,會先觸發擴容而非樹化,避免在小容量數組上過度樹化浪費資源。?

反樹化(紅黑樹轉鏈表)的觸發條件:?

當紅黑樹的節點數量≤6(UNTREEIFY_THRESHOLD = 6)時,會自動退化為鏈表,減少樹結構帶來的維護成本。?

為什么閾值是 8 和 6??

  • 官方注釋提到,根據泊松分布,鏈表長度達到 8 的概率僅為 0.00000006(幾乎是小概率事件),此時樹化收益大于成本。?
  • 用 6 作為反樹化閾值,是為了避免鏈表在 8 附近頻繁樹化 / 反樹化( hysteresis 機制)。?

三、源碼直擊:put () 與 resize () 核心邏輯?

3.1 put () 方法:元素插入全流程

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {Node<K,V>[] tab; Node<K,V> p; int n, i;// 步驟1:數組為空則初始化(第一次put時觸發)if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;// 步驟2:計算索引,若桶為空則直接插入新節點if ((p = tab[i = (n - 1) & hash]) == null)tab[i] = newNode(hash, key, value, null);else {Node<K,V> e; K k;// 步驟3:若桶中首個節點的key與插入key相同,直接覆蓋if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))e = p;// 步驟4:若桶中是紅黑樹,則調用樹插入方法else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);// 步驟5:若桶中是鏈表,遍歷鏈表尋找插入位置else {for (int binCount = 0; ; ++binCount) {if ((e = p.next) == null) {p.next = newNode(hash, key, value, null);// 鏈表長度≥8,觸發樹化if (binCount >= TREEIFY_THRESHOLD - 1)treeifyBin(tab, hash);break;}// 找到相同key的節點,跳出循環if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;p = e;}}// 步驟6:若存在相同key的節點,替換valueif (e != null) {V oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}++modCount;// 步驟7:元素數量超過閾值,觸發擴容if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

3.2 resize () 方法:擴容核心邏輯

final Node<K,V>[] resize() {Node<K,V>[] oldTab = table;int oldCap = (oldTab == null) ? 0 : oldTab.length;int oldThr = threshold;int newCap, newThr = 0;// 計算新容量和新閾值(省略細節)if (oldCap > 0) {newCap = oldCap << 1; // 容量翻倍newThr = oldThr << 1; // 閾值翻倍} else if (oldThr > 0) {newCap = oldThr;} else {newCap = 16; // 默認初始容量newThr = 12; // 默認初始閾值(16*0.75)}// 創建新數組Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];table = newTab;// 遷移舊元素到新數組(省略鏈表和紅黑樹的遷移細節)if (oldTab != null) {for (int j = 0; j < oldCap; ++j) {Node<K,V> e;if ((e = oldTab[j]) != null) {oldTab[j] = null;// 單節點直接遷移if (e.next == null)newTab[e.hash & (newCap - 1)] = e;// 鏈表和紅黑樹的遷移邏輯(見上文)// ...}}}return newTab;
}

四、高頻面試題:HashMap 的那些 "為什么"?

4.1 為什么 HashMap 的容量必須是 2 的冪??

  • 確保(n-1) & hash等價于hash % n,用高效的位運算替代取模。?
  • 擴容時可通過hash & oldCap快速判斷元素是否需要遷移(結果為 0 則留在原位置,否則遷移到j+oldCap)。?

4.2 為什么選擇紅黑樹而非 AVL 樹??

  • 紅黑樹:插入和刪除時最多需要 2 次旋轉,調整成本低,適合頻繁插入刪除的場景。?
  • AVL 樹:是嚴格平衡樹,查詢效率更高,但插入刪除可能需要多次旋轉,調整成本高。?

HashMap 更注重整體的插入刪除效率,因此選擇紅黑樹。?

4.3 HashMap 為什么線程不安全?如何解決??

  • 線程不安全表現:多線程并發擴容時,JDK1.7 中可能出現鏈表環(導致死循環);JDK1.8 中雖解決了環問題,但仍可能出現數據覆蓋。?
  • 線程安全方案:?
  • 使用ConcurrentHashMap(JDK1.7 分段鎖,JDK1.8 CAS+ synchronized)。?
  • 通過Collections.synchronizedMap(new HashMap<>())包裝(性能較差,不推薦)。?

4.4 負載因子為什么默認是 0.75??

  • 負載因子過小(如 0.5):哈希沖突少,但數組擴容頻繁,浪費空間。?
  • 負載因子過大(如 1.0):空間利用率高,但哈希沖突概率劇增,查詢效率下降。?

0.75 是時間與空間成本的平衡點,由泊松分布計算得出,此時哈希沖突概率較低。?

五、總結?

JDK1.8 的 HashMap 通過數組 + 鏈表 + 紅黑樹的復合結構,結合哈希函數的擾動優化、高效擴容機制和樹化策略,實現了 "查詢快、插入快、沖突少" 的核心目標。其設計細節(如 2 的冪容量、0.75 負載因子、8/6 樹化閾值)處處體現了 "平衡取舍" 的設計思想。?

理解這些底層原理,不僅能幫助我們在開發中規避 HashMap 的使用陷阱(如 key 未重寫 hashCode/equals 導致的內存泄漏),更能在面試中脫穎而出。建議結合源碼調試,觀察哈希沖突、擴容、樹化的實際過程,加深理解。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/diannao/93131.shtml
繁體地址,請注明出處:http://hk.pswp.cn/diannao/93131.shtml
英文地址,請注明出處:http://en.pswp.cn/diannao/93131.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

【element-ui】HTML引入本地文件出現font找不到/fonts/element-icons.woff

文章目錄目錄結構問題復現解決辦法目錄結構 |-web|- public|- lib|- ...|- index.htmlindex.html <!DOCTYPE html> <html> <head><meta charset"UTF-8"><!-- import CSS --><link rel"stylesheet" href"./public/…

Windows|CUDA和cuDNN下載和安裝,默認安裝在C盤和不安裝在C盤的兩種方法

本篇文章將詳細介紹在Windows操作系統中配置CUDA和cuDNN的步驟。通過本教程&#xff0c;您將能夠輕松完成CUDA和cuDNN的安裝、環境變量配置以及與深度學習框架&#xff08;如TensorFlow和PyTorch&#xff09;兼容性測試&#xff0c;從而為您的深度學習項目提供強大的硬件支持。…

Vue 項目動態接口獲取翻譯數據實現方案(前端處理語言翻譯 vue-i18n)

在大型多語言項目中&#xff0c;將翻譯數據硬編碼在項目中往往不夠靈活。通過接口動態獲取翻譯數據&#xff0c;并結合本地緩存提升性能&#xff0c;是更優的國際化實現方式。本文將詳細介紹如何在 Vue 項目中實現這一方案。 方案優勢 靈活性高&#xff1a;翻譯內容更新無需修改…

Mybatis-plus多數據源

適用于多種場景&#xff1a;純粹多庫、 讀寫分離、 一主多從、 混合模式等目前我們就來模擬一個純粹多庫的一個場景&#xff0c;其他場景類似場景說明&#xff1a;我們創建兩個庫&#xff0c;分別為&#xff1a; mybatis_plus&#xff08;以前的庫不動&#xff09;與my…

廣東省省考備考(第五十六天7.25)——常識:科技常識(聽課后強化訓練)

錯題解析解析解析解析解析解析解析解析解析標記題解析解析今日題目正確率&#xff1a;40%

RabbitMQ簡述

RabbitMQ簡述 RabbitMQ 是一個開源的 消息代理&#xff08;Message Broker&#xff09; 軟件&#xff0c;實現了 高級消息隊列協議&#xff08;AMQP&#xff09;&#xff0c;用于在分布式系統中存儲、轉發消息&#xff0c;支持異步通信、解耦服務、負載均衡和消息緩沖。 核心…

skywalking應用性能監控

1.skywalking描述 官方文檔 SkyWalking 是一個開源的可觀測性平臺&#xff0c;用于收集、分析、匯總和可視化來自服務及云原生基礎設施的數據。SkyWalking 為維護分布式系統的清晰視圖提供了簡便的方法&#xff0c;即使是在跨云環境中也能做到。它是一款專為云原生、基于容器的…

如何徹底清除服務器上的惡意軟件與后門

清除服務器上的惡意軟件與后門 是確保服務器安全的關鍵步驟。惡意軟件和后門可能導致數據泄露、性能下降&#xff0c;甚至服務器被攻擊者完全控制。以下是徹底清除惡意軟件與后門的詳細指南&#xff0c;包括 檢測、清理、修復與預防 的步驟。1. 徹底清除惡意軟件與后門的步驟1.…

Linux和Windows基于V4L2和TCP的QT監控

最近工作需要用QT做一個網絡攝像頭測試&#xff0c;簡單記錄&#xff1a;服務端&#xff0c;主機配置為Ubuntu&#xff0c;通過端口12345采集傳輸MJPEG格式圖片windows客戶端&#xff0c;QT Creator通過ip地址連接訪問提前準備服務端需要安裝QT5sudo apt-get install qt5-defau…

yolo格式

labelimg中的格式yolo格式id 框中心點X對于總圖片的比例 框中心點Y對于總圖片的比例 框X總長度對于總圖片的比例 框Y總長度對于總圖片的比例

Day 8-zhou R包批量安裝小補充!!!

BiocManager::install(c(“S4Vectors”, “BiocGenerics”)) 以下是使用BiocManager安裝S4Vectors和BiocGenerics包的詳細步驟。這些步驟基于最新的Bioconductor和R版本&#xff08;R 4.5&#xff09;。 安裝步驟安裝BiocManager 如果你還沒有安裝BiocManager&#xff0c;可以使…

電商項目_核心業務_數據歸檔

無論采用哪種存儲系統&#xff0c;數據查詢的耗時取決于兩個因素查找的時間復雜度數據總量查找的時間復雜度又取決于查找算法數據存儲結構以Mysql存儲的訂單數據為例&#xff0c;隨著業務的發展&#xff0c;數據量越來越大&#xff0c;對一些歷史歸檔數據的查詢&#xff0c;如果…

第十講:stack、queue、priority_queue以及deque

目錄 1、stack 1.1、stack的使用 1.2、stack的OJ題 1.2.1、最小棧 1.2.2、棧的壓入彈出序列 1.2.3、逆波蘭表達式求值 1.3、stack的模擬實現 2、queue 2.1、queue的使用 2.2、queue的OJ題 2.2.1、二叉樹的層序遍歷 2.3、queue的模擬實現 3、priority_queue 3.1、…

如何思考一個動態規劃問題需要幾個狀態?

如何思考一個動態規劃問題需要幾個狀態&#xff1f;第一步&#xff1a;思考 角色第二步&#xff1a;考慮 過去的影響第三步&#xff1a;畫出狀態轉移圖第四步&#xff1a;寫出狀態轉移方程第五步&#xff1a;驗證是否能覆蓋所有路徑 邊界幾個常見題目總結&#xff1a;第一步&a…

【每天一個知識點】生成對抗聚類(Generative Adversarial Clustering, GAC)

&#x1f4d8; 生成對抗聚類&#xff08;Generative Adversarial Clustering, GAC&#xff09; 一、研究背景與動機 聚類是無監督學習中的核心任務。傳統方法如 K-means、GMM、DBSCAN 等難以適應高維、非線性、復雜結構數據。 生成對抗聚類&#xff08;GAC&#xff09; 融合…

Qt 窗口 工具欄QToolBar、狀態欄StatusBar

每日激勵&#xff1a;“不設限和自我肯定的心態&#xff1a;I can do all things。 — Stephen Curry” 緒論?&#xff1a; 一段時間沒有更新&#xff0c;這段時間一直在忙各種事情&#xff0c;后續將再次上路持續更新C相關知識 本章將繼續前面的QT篇章&#xff0c;本章主要講…

FFmpeg——參數詳解

FFmpeg參數詳解一、基本命令結構1.1、查詢參數1.1.1、version1.1.2、buildconf1.1.3、devices1.1.4、formats1.1.5、muxers1.1.6、demuxers1.1.7、codecs1.1.8、decoders1.1.9、encoders1.1.10、bsfs1.1.11、protocols1.1.12、filters1.1.13、pix_fmts1.1.14、layouts1.1.15、s…

流媒體傳輸:RTSP傳輸詳解(包含RTP,RTCP,RTSP詳解)

一、什么是 RTSP?協議 1.1 RTSP 協議簡介? RTSP&#xff0c;全稱實時流傳輸協議&#xff08;Real Time Streaming Protocol&#xff09;&#xff0c;是一種位于應用層的網絡協議。它主要用于在流媒體系統中控制實時數據&#xff08;如音頻、視頻等&#xff09;的傳輸&#…

Python學習-----1.認識Python

目錄 前言 1.關于Python博客前期的內容 2.計算機基礎概念 2.1.什么是計算機? 2.2.什么是編程&#xff1f; 2.3.編程語言有哪些&#xff1f; 3.Python背景知識 3.1.Python是怎么來的&#xff1f; 3.2.Python都可以用來干什么&#xff1f; 3.3.Python的優缺點 3.4.Py…

MongoDB頻繁掉線頻繁斷開服務的核心原因以及解決方案-卓伊凡|貝貝|莉莉|糖果

MongoDB頻繁掉線頻繁斷開服務的核心原因以及解決方案-卓伊凡|貝貝|莉莉|糖果查看日志內容 &#xff1a;2025-07-22T17:05:20.2160800 I CONTROL [initandlisten] MongoDB starting : pid34231 port28018 dbpath/data/mongodb 64-bit hostVM-0-17-centos 2025-07-22T17:05:20.21…