【Java】HashMap的詳細介紹

目錄

一.HashMap

1.基本概念

2.底層數據結構:

3.HashCode和equals方法

為什么重寫HashCode方法?

為什么重新equals方法?

4.put操作

1.初始化和數組檢查

2.計算索引并檢查桶是否為空

3.桶不為null,處理哈希沖突

4.判斷鏈表是否轉化紅黑樹

5.以及數組大小是否超過閾值進行擴容

5.get操作

java1.7 -- java1.8 HashMap 做了哪些優化:

哈希值計算:

鏈表插入方式:

二.ConcurrentHashMap

1.Java 1.7?ConcurrentHashMap架構:

2.Java 1.8?ConcurrentHashMap架構:

CAS+synchronized的使用場景:

擴容區別:

size()區別:


一.HashMap

1.基本概念

HashMap是基于哈希表實現的Map接口,用于存儲鍵值對(K-V)格式,其核心作用就是通過哈希函數為了更快查詢到某個元素,時間復雜度O(1);

2.底層數據結構:

在java(1.7)之前 底層是采用“數組 + 鏈表” 的形式存儲

但在java(1.8) 之后,變成了“數組 + 鏈表 + 紅黑樹?”形式存儲

我們主要了解java1.8之后的內容;

從源碼上查看

數組的初始容量為16,負載因子為0.75,所以當超過這個閾值 16 * 0.75 = 12?
設計初始容量為16
核心原因是2 的冪次方更適合哈希計算,能減少哈希沖突,但8又太頻繁擴容,新增性能開銷,而32空間又會太大,則浪費空間~
負載因子
也是同理,太小,會頻繁擴容,雖然查詢快了,但是數組太稀疏,浪費空間,而太大,就會擴容少,空間利用率高,但查詢會變慢~


當插入第13個元素的時候,已經超過閾值,為了避免哈希沖突,會進行擴容~

然后這邊當數組大小超過64時候,鏈表大小超過8的時候,會從鏈表進化成紅黑樹,但當元素個數少于6個時,會退回鏈表~

  1. 鏈表長度≥8
    基于泊松分布,當負載因子為 0.75 時,鏈表長度自然增長到 8 的概率極低(約千萬分之一),此時說明哈希沖突異常頻繁,需要轉為紅黑樹優化。

  2. 數組容量≥64
    若數組容量小于 64(如 32),即使鏈表長度≥8,也會先觸發擴容(而非轉紅黑樹)。原因是:數組容量小時,擴容成本低,通過擴容可分散元素,減少沖突;而紅黑樹的維護成本(插入 / 刪除時的旋轉操作)高于擴容,此時擴容更高效。

3.HashCode和equals方法

HashMap中的鍵一定會實現這倆個方法,hashCode用于計算存儲的位置,而equals用于判斷來個鍵是否相同,在put方法的時候,如果倆個哈希值相同,會判斷是否是同一個值,如果不是就會放在同一個桶中的不同位置,如果相同就是一個東西~

為什么重寫HashCode方法?

如果不重寫,這個時候,就會導致倆個相同的key,會被計算出倆個不同的哈希值,導致他們存在在不同的桶中,到時候查詢的時候到底是查哪個?

為什么重新equals方法?

equals方法是為了當出現哈希沖突的時候,倆個key的哈希值相同,放在同一桶中,但沒有比較它們的對象,可能會誤判會導致倆個key存儲在不同位置,所以沒有覆蓋之前的值,就會錯誤~

class Person {String name;int age;@Overridepublic int hashCode() { // 重寫hashCode,保證邏輯相等的對象哈希值相同return Objects.hash(name, age);}// 未重寫equals()
}public static void main(String[] args) {HashMap<Person, String> map = new HashMap<>();Person p1 = new Person("張三", 20);Person p2 = new Person("張三", 20);map.put(p1, "學生");map.put(p2, "工人"); // 哈希值相同,進入同一個桶,但equals判斷為不同keySystem.out.println(map.size()); // 仍輸出2,而非預期的1
}

hashCode()equals()必須配套重寫,且滿足以下規則:

  • 一致性:如果兩個對象通過equals()判斷為相等,則它們的hashCode()必須返回相同的值;
  • 對稱性:如果a.equals(b)true,則b.equals(a)也必須為true
  • 非空性:a.equals(null)必須返回false

4.put操作

1.初始化和數組檢查

首先判斷 HashMap 是否未初始化(table?數組為?null?或長度為 0),若是則觸發?初始化(resize):

2.計算索引并檢查桶是否為空

    如果該位置為空,直接創建新節點插入

    3.桶不為null,處理哈希沖突

    1. 檢查首節點:若首節點的 key 與傳入 key?equals 相等(且哈希值相同),則視為 “重復 key”,記錄該節點(后續替換其 value)。
    2. 遍歷后續節點
      • 若桶是單鏈表(Node):遍歷鏈表,若找到 key 相等的節點,記錄該節點;若遍歷到鏈表尾部仍未找到,則在鏈尾插入新節點(Node)。
      • 若桶是紅黑樹(TreeNode):調用紅黑樹的插入方法(putTreeVal),若找到重復 key 則記錄節點,否則插入新樹節點。
    3. 處理重復 key:若步驟 1 或 2 中找到重復 key 的節點,用新 value 替換其舊 value,流程結束。

    4.判斷鏈表是否轉化紅黑樹

    5.以及數組大小是否超過閾值進行擴容

    5.get操作

    同理get?方法的核心邏輯是:
    哈希計算→定位桶→根據桶結構(鏈表 / 紅黑樹)查找匹配節點

    java1.7 -- java1.8 HashMap 做了哪些優化:

    哈希值計算:

    1.7版本:對 key 的原始哈希值(hashCode()?結果)進行?4 次擾動(多次移位和異或運算)

    1.8版本:簡化為?1 次擾動,僅通過一次 “高 16 位與低 16 位異或” 實現混合

    減少計算開銷:一次異或運算即可達到近似的混合效果,相比 1.7 的 4 次運算,計算效率更高。
    實際效果:在大多數場景下,1 次擾動已能保證哈希值的均勻分布,同時降低了 CPU 運算成本。

    鏈表插入方式:

    由頭插變成尾插,核心是為了解決多線程擴容時的鏈表環化問題,同時讓鏈表操作邏輯更合理。

    多線程擴容時可能導致鏈表環化(死循環)
    擴容過程中,節點會從舊數組遷移到新數組,頭插法在遷移時會反轉鏈表順序(例如舊鏈表 A→B,遷移后新鏈表變為 B→A)。若此時有多個線程同時操作,可能出現節點引用相互指向的情況(如 A.next = B 且 B.next = A),形成環形鏈表。后續查詢時,線程會陷入無限循環,導致 CPU 占用飆升。


    二.ConcurrentHashMap

    ConcurrentHashMap 是 Java 中用于并發場景的哈希表實現,專為高并發環境設計;

    1.Java 1.7?ConcurrentHashMap架構:

    在java1.8之前?ConcurrentHashMap?采用的是?分段數組(Segment)+ 哈希表 , 默認為16個segment,同時支持16個并發~

    • 整體結構:ConcurrentHashMap -> Segment[] -> HashEntry[] -> 鏈表
    • 寫操作(put/remove 等)
      1. 計算 key 的哈希值,定位到對應的?Segment
      2. 獲取該?Segment?的鎖(若被占用則阻塞);
      3. 在?Segment?內部的哈希表中執行插入 / 刪除(類似 HashMap 的邏輯,鏈表頭插法);
      4. 釋放鎖。
    • 優點:通過分段鎖實現了多線程并發寫,效率比全表鎖(如 Hashtable)高得多。
    • 缺點
      • 鎖粒度仍較大(以?Segment?為單位),若多個線程操作同一?Segment,仍會阻塞;
      • 結構復雜,維護成本高;
      • 擴容僅針對單個?Segment,但整體性能受限于?Segment?數量。

    2.Java 1.8?ConcurrentHashMap架構:

    摒棄了?Segment?分段結構,底層直接使用?數組 + 鏈表 + 紅黑樹(與 JDK 1.8 HashMap 結構類似)

    鎖的粒度更小,鎖的是桶的頭節點,并且采取了CAS + synchronized 機制,僅當多個線程操作同一鏈表 / 紅黑樹時才會競爭鎖,鎖沖突概率遠低于 1.7 的 Segment 級鎖。

    CAS+synchronized的使用場景:

    1. 無沖突場景(空桶插入、初始化、低并發計數):用 CAS 實現高效無鎖操作。
    2. 有沖突場景(非空桶操作、復雜結構修改):用 synchronized 加鎖保證原子性。
    版本底層架構哈希表數量鎖粒度
    JDK 1.7 及之前兩層結構:Segment [] 數組 + 每個 Segment 包含一個 HashEntry [] 數組多個(默認 16 個,與 Segment 數量一致)Segment 級(鎖整個子哈希表)
    JDK 1.8 及之后單層結構:Node [] 數組(鏈表 / 紅黑樹解決沖突)1 個(整個 ConcurrentHashMap 共用一個底層數組)節點級(鎖鏈表頭節點或紅黑樹根節點)

    擴容區別:

    特性JDK 1.7 擴容JDK 1.8 擴容
    擴容范圍單個 Segment 獨立擴容整個數組全表擴容
    并發能力單線程擴容(當前操作 Segment 的線程)多線程協同擴容(所有線程可協助遷移)
    鎖影響范圍僅鎖定當前 Segment,其他 Segment 可用無全局鎖,僅鎖定遷移中的桶節點
    遷移效率單個 Segment 遷移,效率較低多線程分片遷移,效率更高
    節點遷移方式頭插法(可能反轉鏈表)尾插法(保持鏈表順序,無環化風險)
    與讀寫的兼容性擴容時該 Segment 讀寫阻塞擴容與讀寫可并行(讀無鎖,寫鎖單個桶)

    size()區別:

    JDK 1.7 中,ConcurrentHashMap 由多個?Segment?組成,每個?Segment?獨立維護自己的元素數量(count),因此計算總?size?時需要累加所有 Segment 的 count。

    • 為減少誤差,JDK 1.7 采用 “重試機制”:如果兩次連續累加的結果一致,則認為是準確值;若不一致(說明期間有并發修改),最多重試 3 次,若仍不一致則直接返回當前累加值(接受一定誤差)。

    而在JDK 1.8中,計算?size?依賴于?baseCount?原子變量和?counterCells?輔助數組,通過無鎖累加實現,返回兩者的總和作為最終的?size

    當插入元素成功后,需要將總數?+1,流程如下:

    1. 優先嘗試更新?baseCount
      線程通過 CAS 操作直接更新?baseCountbaseCount + 1)。

      • 若 CAS 成功:計數完成,無需其他操作。
      • 若 CAS 失敗:說明存在并發競爭(多個線程同時更新?baseCount),進入下一步。
    2. 競爭激烈時,使用?counterCells?分散計數

      • 若?counterCells?未初始化,先通過 CAS 初始化(創建一個?CounterCell?數組)。
      • 線程通過哈希算法(基于線程 ID 或隨機數)隨機選擇?counterCells?中的一個?CounterCell
      • 嘗試用 CAS 更新該?CounterCell?的?valuevalue + 1):
        • 若成功:計數完成。
        • 若失敗:重試或選擇其他?CounterCell(避免死鎖)。
    特性JDK 1.7 計數方式JDK 1.8 計數方式
    底層依賴各 Segment 的?count?累加baseCount?+?counterCells?累加
    并發處理重試機制減少誤差,返回近似值CAS 原子操作,返回接近精確值
    性能低并發時高效,依賴 Segment 數量高低并發均高效,通過分散競爭優化
    實現復雜度簡單(遍歷 + 重試)復雜(原子變量 + 輔助數組 + 競爭分散)
    適用場景分段鎖架構下的近似計數全局數組架構下的高效精確計數

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

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

    相關文章

    nifi 增量處理組件

    在Apache NiFi中&#xff0c;QueryDatabaseTable 是一個常用的處理器&#xff0c;主要用于從關系型數據庫表中增量查詢數據&#xff0c;特別適合需要定期抽取新增或更新數據的場景&#xff08;如數據同步、ETL流程&#xff09;。它的核心功能是通過跟蹤指定列的最大值&#xff…

    【數據可視化-90】2023 年城鎮居民人均收入可視化分析:Python + pyecharts打造炫酷暗黑主題大屏

    &#x1f9d1; 博主簡介&#xff1a;曾任某智慧城市類企業算法總監&#xff0c;目前在美國市場的物流公司從事高級算法工程師一職&#xff0c;深耕人工智能領域&#xff0c;精通python數據挖掘、可視化、機器學習等&#xff0c;發表過AI相關的專利并多次在AI類比賽中獲獎。CSDN…

    Multiverse模型:突破多任務處理和硬件效率瓶頸的AI創新(上)

    隨著人工智能技術的快速發展&#xff0c;多模態模型成為了當前研究的熱點。多模態模型的核心思想是能夠同時處理和理解來自不同模態&#xff08;如文本、圖像、音頻等&#xff09;的數據&#xff0c;從而為模型提供更加全面的語境理解和更強的泛化能力。 楊新宇&#xff0c;卡…

    OpenCV 高斯模糊降噪

    # 高斯模糊處理(降噪) # 參數1: 原始圖像 # 參數2: 高斯核尺寸(寬,高&#xff0c;必須為正奇數) # 其他模糊方法: # - cv.blur(): 均值模糊 # - cv.medianBlur(): 中值模糊 # - cv.bilateralFilter(): 雙邊濾波 blur cv.GaussianBlur(img, (7,7), cv…

    常見通信協議詳解:TCP、UDP、HTTP/HTTPS、WebSocket 與 RPC

    在現代網絡通信中&#xff0c;各種協議扮演著至關重要的角色&#xff0c;它們決定了數據如何在網絡中傳輸、控制其可靠性、實時性與適用場景。對于開發者而言&#xff0c;理解這些常見的通信協議&#xff0c;不僅有助于更好地設計系統架構&#xff0c;還能在面對不同業務需求時…

    深入解析MPLS網絡中的路由器角色

    一、 MPLS概述&#xff1a;標簽交換的藝術 在深入角色之前&#xff0c;我們首先要理解MPLS的核心思想。傳統IP路由是逐跳進行的&#xff0c;每一臺路由器都需要對數據包的目的IP地址進行復雜的路由表查找&#xff08;最長匹配原則&#xff09;&#xff0c;這在網絡核心層會造成…

    AI的拜師學藝,模型蒸餾技術

    AI的拜師學藝&#xff0c;模型蒸餾技術什么是模型蒸餾&#xff0c;模型蒸餾是一種高效的模型壓縮與知識轉移方法&#xff0c;通過將大型教師模型的知識精煉至小型學生模型&#xff0c;讓學生模型模仿教師模型的行為和內化其知識&#xff0c;在保持模型性能的同時降低資源消耗。…

    Python爬蟲從入門到精通(理論與實踐)

    目錄 1. 爬蟲的魅力:從好奇心到數據寶藏 1.1 爬蟲的基本流程 1.2 準備你的工具箱 2. 第一個爬蟲:抓取網頁標題和鏈接 2.1 代碼實戰:用requests和BeautifulSoup 2.2 代碼解析 2.3 遇到問題怎么辦? 3. 進階爬取:結構化數據抓取 3.1 分析網頁結構 3.2 代碼實戰:抓取…

    【DDIA】第三部分:衍生數據

    1. 章節介紹 本章節是《設計數據密集型應用》的第三部分&#xff0c;聚焦于多數據系統集成問題。前兩部分探討了分布式數據庫的基礎內容&#xff0c;但假設應用僅用一種數據庫&#xff0c;而現實中大型應用常需組合多種數據組件。本部分旨在研究不同數據系統集成時的問題&#…

    Spring配置線程池開啟異步任務

    一、單純使用Async注解。1、Async注解在使用時&#xff0c;如果不指定線程池的名稱&#xff0c;則使用Spring默認的線程池&#xff0c;Spring默認的線程池為SimpleAsyncTaskExecutor。2、方法上一旦標記了這個Async注解&#xff0c;當其它線程調用這個方法時&#xff0c;就會開…

    AI數據倉庫優化數據管理

    內容概要AI數據倉庫代表了現代企業數據管理的重大演進&#xff0c;它超越了傳統數據倉庫的范疇。其核心在于利用人工智能技術&#xff0c;特別是機器學習和深度學習算法&#xff0c;來智能化地處理從多源數據整合到最終價值提取的全過程。這種新型倉庫不僅能高效地統一存儲來自…

    SpringMVC(詳細版從入門到精通)未完

    SpringMVC介紹 MVC模型 MVC全稱Model View Controller,是一種設計創建Web應用程序的模式。這三個單詞分別代表Web應用程序的三個部分: Model(模型):指數據模型。用于存儲數據以及處理用戶請求的業務邏輯。在Web應用中,JavaBean對象,業務模型等都屬于Model。 View(視圖…

    vue3運行機制同tkinter做類比

    把剛才“Vue3 蓋別墅”的故事&#xff0c;和 Python 的 tkinter 做一個“一一對應”的翻譯&#xff0c;你就能瞬間明白兩件事的異同。 為了直觀&#xff0c;用同一棟房子比喻&#xff1a; Vue3 的“網頁” ? tkinter 的“桌面窗口”瀏覽器 ? Python 解釋器 Tcl/Tk 引擎 下面…

    Fastadmin后臺列表導出到表格

    html中添加按鈕<a href"javascript:;" class"btn btn-success btn-export" title"{:__(導出數據)}" ><i class"fa fa-cloud-download"></i> {:__(導出數據)}</a>對應的js添加代碼處理點擊事件&#xff0c;添加…

    Nginx反向代理與緩存實現

    1. Nginx反向代理核心配置解析 1.1 反向代理基礎配置結構 Nginx反向代理的基礎配置結構主要包括server塊和location塊的配置。一個典型的反向代理配置示例如下&#xff1a; server {listen 80;server_name example.com;location / {proxy_pass http://backend_servers;proxy_se…

    第2節 如何計算神經網絡的參數:AI入門核心邏輯詳解

    ?? 核心目標:找到最佳w和b! 上期咱們聊了神經網絡就是復雜的"線性變換+激活函數套娃",今天的重頭戲就是:怎么算出讓模型完美擬合數據的w(權重)和b(偏置)!先從最簡單的線性函數說起,一步步揭開神秘面紗 那么如何計算w和b呢?首先明確我們需要的w和b能夠讓…

    AutoSar AP平臺功能組并行運行原理

    在 AUTOSAR Adaptive Platform&#xff08;AP&#xff09;中&#xff0c;同一個機器上可以同時運行多個功能組&#xff08;Function Groups&#xff09;&#xff0c;即使是在單核CPU環境下。其調度機制與進程調度既相似又存在關鍵差異&#xff0c;具體實現如下&#xff1a;功能…

    linux服務器查看某個服務啟動,運行的時間

    一 查看服務啟動運行時間1.1 查看啟動時間查看啟動時間&#xff08;精確到秒&#xff09;&#xff1a;ps -p <PID> -o lstart例子如下&#xff1a;ps -p 1234 -o lstart1.2 查詢運行時長ps -p <PID> -o etimeps -p 1234 -o etime1.3 總結

    【JS 性能】前端性能優化基石:深入理解防抖(Debounce)與節流(Throttle)

    【JS 性能】前端性能優化基石&#xff1a;深入理解防抖&#xff08;Debounce&#xff09;與節流&#xff08;Throttle&#xff09; 所屬專欄&#xff1a; 《前端小技巧集合&#xff1a;讓你的代碼更優雅高效》 上一篇&#xff1a; 【JS 語法】代碼整潔之道&#xff1a;解構賦值…

    線性代數 · 直觀理解矩陣 | 空間變換 / 特征值 / 特征向量

    注&#xff1a;本文為 “線性代數 直觀理解矩陣” 相關合輯。 英文引文&#xff0c;機翻未校。 如有內容異常&#xff0c;請看原文。 Understanding matrices intuitively, part 1 直觀理解矩陣&#xff08;第一部分&#xff09; 333 March 201120112011 William Gould Intr…