通俗易懂解釋Java8 HashMap

我們來用通俗易懂的方式解釋一下 Java 8 中 HashMap 的原理,讓你對它的結構、運行機制有清晰的理解。


🌳 什么是 HashMap?

HashMap 是 Java 中非常常用的數據結構,用于存儲鍵值對(key-value)。你可以把它理解成一個“大數組”,每個位置可以存儲一個或多個數據。


🧱 HashMap 的基本結構

Java 8 中的 HashMap 底層由 數組 + 鏈表 + 紅黑樹 組成:

HashMap└── 數組 table[]     ← 每個元素稱為“桶”├── 鏈表/樹    ← 存儲實際的 key-value 節點(Node)

🛠? 核心工作流程

我們來一步步看 HashMap 是怎么工作的:


? 插入 put(key, value)

舉個例子:

map.put("apple", 100);

背后做了這些事:

  1. 計算 hash 值:

    • 對 key(“apple”)調用 hashCode(),然后做了一些擾動處理。
    • 比如 "apple".hashCode() = 93029210,經過擾動后變成一個新的 hash 值。
  2. 計算數組索引:

    • index = hash & (table.length - 1),定位到數組的某一桶。
  3. 判斷該桶是否有元素:

    • 如果沒有:直接放進去。

    • 如果有:判斷 key 是否相同。

      • 相同:替換舊值。
      • 不同:鏈表/紅黑樹追加新節點。
  4. 鏈表轉紅黑樹(Java8 新特性):

    • 當同一個桶內元素數量超過 8 個 且數組長度超過 64,會把鏈表轉成紅黑樹,提高查詢效率。

🔍 查詢 get(key)

比如:

map.get("apple");
  1. 計算 hash。

  2. 定位到數組索引。

  3. 在對應的桶里找:

    • 是鏈表:順序比較 key。
    • 是紅黑樹:用樹的方式查找。

🔄 發生了哈希沖突怎么辦?

多個不同的 key 計算后可能落到同一個數組位置(桶),這就叫 哈希沖突。Java 8 中:

  • 前幾個沖突的 key 用 鏈表 掛在一起;
  • 如果沖突太多(鏈表長度 > 8 且數組長度 > 64),鏈表就會轉成 紅黑樹,加快查找速度。

📈 擴容機制

HashMap 的默認容量是 16,裝載因子是 0.75,當元素數量 > 16 * 0.75 = 12 時,就會發生 擴容

  1. 擴容為原來容量的 2 倍。
  2. 重新計算所有 key 的 hash 值和位置,遷移元素。

這也是為什么我們建議在使用 HashMap 時 預估容量大小,以避免多次擴容影響性能。


🧠 為什么要用紅黑樹?

因為鏈表查找是 O(n),而紅黑樹是 O(log n),當沖突嚴重時,查詢效率會大幅下降。引入紅黑樹可以在極端情況下保持較好的性能。


🔥 小結圖示:

插入:hash → 定位數組桶 → 空就放,有就比較 → 鏈表 / 紅黑樹
查詢:hash → 定位 → 鏈表/樹中查找 key
擴容:元素太多 → 數組翻倍 → 元素重新分布

? Java 8 中 HashMap 的優點

特性優點說明
引入紅黑樹避免鏈表過長導致查找變慢
擾動函數優化減少哈希沖突,分布更均勻
結構清晰數組 + 鏈表 + 紅黑樹,靈活擴展

我們來用通俗易懂的語言 + 舉例方式,深入講講 Java 8 中 HashMap擾動函數(hash 擾動函數),幫助你真正理解它的作用和原理。


一、什么是擾動函數?

? 定義一句話:

擾動函數是為了讓 hash 值的高位信息也能參與數組下標的計算,從而讓 key 更均勻地分布在 HashMap 的數組中。


二、為什么需要擾動函數?

🚨 問題:低位碰撞嚴重!

在 HashMap 中,定位數組下標的方式是這樣的:

index = hash(key) & (table.length - 1)

由于 table.length 是 2 的冪,這樣的 & 操作會只保留 hash 值的低位

?? 如果兩個 key 的 hash 值只有高位不同,而低位相同,它們就會落入同一個桶(發生沖突)。

🧠 舉個例子:

hash1 = 0b10110000_00000000_00000000_00000001
hash2 = 0b01010000_00000000_00000000_00000001

雖然高位差很多,但最后的幾位一樣。由于我們只用低位參與數組下標計算,這兩個 key 會落在同一個位置!

? 解決方案:擾動函數

通過擾動函數把 高位信息混合到低位,避免沖突更均勻。


三、Java 8 中的擾動函數源碼分析

HashMap 中源碼如下(簡化):

static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

🚀 解釋:

  • h = key.hashCode():拿到 key 的原始 hashCode(32 位整數)。
  • h >>> 16:無符號右移 16 位,把高位移動到低位。
  • ^:做異或運算,把原來的高位和低位混合起來,讓高位也參與到最終結果中

四、通俗舉例講解

假設我們有兩個 key:

key1.hashCode() = 0b10110000_00000000_00000000_00000001
key2.hashCode() = 0b01010000_00000000_00000000_00000001

它們的低位是一樣的,都會落到相同數組位置,發生 hash 沖突。

有了擾動函數:

擾動結果 = h ^ (h >>> 16)

擾動后:

  • key1 的高位會被移到低位并參與混合;
  • 即便低位一樣,只要高位不同,擾動后結果就不一樣。

這樣就打亂了 hashCode 的原始規律,使得分布更隨機,降低 hash 沖突概率。


五、總結(口訣記憶)

  • 🚩 低位計算索引,高位白白浪費。
  • 🧠 擾動函數混高低,分布均勻更合理。
  • ? 異或右移搞起來,沖突少,效率高。

六、動手試一試(簡單代碼示例)

public class HashDisturbExample {public static void main(String[] args) {String key = "apple";int h = key.hashCode();int disturbed = h ^ (h >>> 16);System.out.println("原始 hashCode: " + h);System.out.println("擾動后結果:     " + disturbed);}
}

運行后你會看到兩個不同的值,這說明擾動函數確實改變了 hash 值結構,幫助分布更均勻。


? 第一個問題:為什么 table.length 是 2 的冪?

🔧 原因:

為了 優化取模(mod)操作為位運算(&)操作,提升性能!

🧠 解釋:

HashMap 中,我們要根據 key 的 hash 值決定存放的位置,也就是數組下標:

index = hash(key) % table.length

如果 table.length 是任意數字,比如 10,那就必須使用 除法運算 %,這是一個比較慢的操作。

而如果 table.length 是 2 的冪,比如 16、32、64,我們就可以寫成:

index = hash(key) & (table.length - 1)

?? 這行代碼的效果和 % 是一樣的,但效率更高

🧠 舉例說明:

假設 table.length = 16(即 2?),那么 16 的二進制是:

16 = 10000(即 2 的 4 次方)
16 - 1 = 15 = 01111(二進制)

這樣做 & 15 相當于取 hash 值的 低 4 位,效果等同于 hash % 16,而且更快!


? 第二個問題:為什么 & 的操作和 % 是一樣的?

🎯 只有在 table.length2 的冪時,才成立!

hash % 16 == hash & (16 - 1)

這是因為 2 的冪次減 1 得到的是全 1 的二進制位數。比如:

table.length二進制table.length - 1二進制
8100070111
16100001501111
3210000031011111

當你對一個數 x 執行 x & (n - 1),就等于 取 x 的低幾位,這等價于 x % n(當 n 是 2 的冪時)。

? 舉個例子:

hash = 0b101010        // 十進制 42
table.length = 16      // 2?
mask = 16 - 1 = 15 = 0b111142 % 16 = 10
42 & 15 = 0b101010 & 0b1111 = 0b1010 = 10 ?

結果一樣,但是 & 運算效率遠高于 % 運算。


? 第三個問題:為什么是“低位”參與運算?

📌 因為 hash & (table.length - 1) 只保留 低位信息

你剛剛也看到了,舉個例子:

hash = 0b10110000_00000000_00000000_00001101
table.length = 16 (2?),mask = 0b00000000_00000000_00000000_00001111

執行 &

hash & mask =
0b10110000_00000000_00000000_00001101
&
0b00000000_00000000_00000000_00001111
=
0b00000000_00000000_00000000_00001101 => 13

?? 所以只有 低 4 位被保留下來,高位被舍棄了!


🤔 那高位的 hash 值不是白計算了嗎?

是的,如果不做處理,高位信息就浪費了,這就是為啥我們需要:

🚀 擾動函數:h ^ (h >>> 16)

它會把高 16 位的信息也混入低 16 位中,確保高位不會被浪費。


? 總結歸納:

問題解答
為什么 table.length 是 2 的冪?為了將取模 % 優化成更快的 & 位運算。
為什么 & 可以代替 %當除數是 2 的冪時,x & (n - 1) 等價于 x % n,而效率更高。
為什么是低位參與?因為 & 操作只保留了低位的信息(如 & 15 保留低 4 位),高位信息被忽略。

🧠 為什么要擴容?

HashMap 是通過數組 + 鏈表(或紅黑樹)來存儲鍵值對的。它的性能依賴于:

鍵值對在數組中的分布是否均勻(沖突少)

如果 HashMap 存放的元素太多,沖突越來越多,鏈表變長,性能會急劇下降,所以需要:

擴容(resize):將原數組變大,把原來的元素“重新分布”到新的數組中。


?? HashMap 擴容時機

HashMap 會在滿足以下條件時擴容:

當前元素個數 > 閾值(threshold) = 容量 × 負載因子(loadFactor)
  • 默認初始容量是 16
  • 默認負載因子是 0.75
  • 所以默認第一次擴容是在元素數量超過 16 × 0.75 = 12 時發生

🚀 擴容做了什么?

當發生擴容時,HashMap 會做幾件事:

  1. 將數組容量擴大為 原來的兩倍
  2. 創建一個新的數組(新的 table)
  3. 重新計算每個鍵值對的索引(index)
  4. 將所有舊元素 “搬家” 到新數組中

📊 舉個例子來說明

假設你有一個容量為 4 的 HashMap,插入了 3 個元素(已接近 0.75 的閾值):

原數組(長度4):
index 0: [A -> 1]
index 1: [B -> 2]
index 2: [C -> 3]
index 3: null

你再插入一個元素 D -> 4,此時元素數目為 4,超過 4 × 0.75 = 3,觸發擴容。

擴容過程:

  • 數組變為長度 8
  • 所有鍵的 hash 會重新計算 index
  • 每個鍵值對會根據新的 index 放到新數組中,分布更均勻
擴容后數組(長度8):
index 0: null
index 1: [A -> 1]
index 2: null
index 3: [C -> 3]
index 4: null
index 5: [B -> 2]
index 6: [D -> 4]
index 7: null

這樣就避免了原來在小數組中“擁擠”的問題。


🔁 元素“搬家”的優化:位運算重分布

Java 8 做了一個優化,在擴容時 不再重新計算 hash 值,而是通過如下邏輯判斷元素是留在原位置還是移動到“新位置”:

// 假設 oldCap 是原數組長度,newCap = oldCap * 2
if ((e.hash & oldCap) == 0) {// 留在原地
} else {// 移動到原位置 + oldCap
}

這個技巧利用了 2 的冪次 的特性,通過 hash & oldCap 直接判斷是否需要挪動,提高了效率!


💡 為什么擴容不是一開始就做得很大?

為了節省內存。HashMap 的容量和負載因子設計是為了在性能和內存之間做權衡。


? 總結:HashMap 擴容機制

步驟內容
擴容時機元素個數 > 閾值(容量 × 負載因子)
擴容操作數組變為原來的兩倍
元素處理每個元素根據新數組重新定位(高效位運算)
優化手段hash & oldCap 判斷是否搬家
目的降低 hash 沖突,提高查詢性能

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

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

相關文章

macOS安裝配置Unbound DNS完整指南

文章目錄macOS安裝配置Unbound DNS完整指南🎯 為什么選擇Unbound?📋 系統要求🚀 安裝步驟1. 使用Homebrew安裝2. 查看安裝信息?? 基礎配置1. 備份默認配置2. 創建基礎配置文件3. 基礎配置內容配置53端口版本(高級用戶…

學習模板元編程(2)std::true_type/false_type

目錄 實現原理 應用場景 條件編譯 通過特化和繼承,實現std::is_xxx系列 思路 舉例 例子1,is_bool 例子2,is_ptr 實現原理 std::true_type/false_type是模板intergral_constant的兩種實現: using true_type integral_co…

Chain-of-Thought Prompting Elicits Reasoning in Large Language Models論文閱讀筆記

Chain-of-Thought Prompting Elicits Reasoning in Large Language Models 摘要 本文探索了思維鏈(chain of thought),即一系列中間推理過程,可以有效地增強大語言模型的復雜推理能力。 在三個大型語言模型上的實驗表明&#xff0…

華為核心交換機S7700的內存OID

華為S7700系列交換機 SNMP內存相關OID說明 以下列出了華為S7700核心交換機在SNMP v2c下可用的內存相關OID,包括CPU內存利用率、物理內存總量、已用內存和空閑內存,并給出每個OID的功能描述、數據類型、單位、使用說明等信息。 1. CPU內存利用率(處理器內存占用百分比) OID名…

中州養老Day02:服務管理護理計劃模塊

本日任務:服務管理的后端開發 1.學習:護理項目 (1)評估開發工期的思路和注意事項 全面熟悉項目,了解項目重點,設置開發優先級 比如,在下面圖片的接口文檔中版本有1.0,2.0,3.0也就是功能的初代,二代,三代,所以我們在大致瀏覽所有功能后,要優先關注初代功能的實現 開發計劃 …

JavaScript:Ajax(異步通信技術)

一、Ajax 核心概念Ajax(Asynchronous JavaScript and XML)是一種異步通信技術,核心特點:無刷新更新:無需重新加載整個頁面異步處理:后臺發送/接收數據不阻塞用戶數據格式:支持 XML/JSON/HTML/純…

leetcode 118. 楊輝三角 簡單

給定一個非負整數 numRows&#xff0c;生成「楊輝三角」的前 numRows 行。在「楊輝三角」中&#xff0c;每個數是它左上方和右上方的數的和。示例 1:輸入: numRows 5 輸出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]示例 2:輸入: numRows 1 輸出: [[1]]提示:1 < numRows…

jmeter--While控制器--循環直到接口響應符合條件

場景描述業務場景&#xff1a;單據計算接口情況&#xff1a;單據計算&#xff0c;調用接口1發起計算&#xff0c;接口2查詢計算執行結果jmeter腳本&#xff1a;把接口1和接口2&#xff08;接口2循環調用&#xff0c;直到返回執行完成狀態&#xff09;添加到一個事務&#xff0c…

組播 | 不同 VLAN 間數據轉發實現邏輯 / 實驗

注&#xff1a;本文為 “不同 vlan 間組播數據轉發” 相關合輯。 圖片清晰度受引文原圖所限。 略作重排&#xff0c;如有內容異常&#xff0c;請看原文。 組播 VLAN&#xff1a;解決路由器為不同 VLAN 用戶復制多份流量問題 aiaiai010101 于 2018-11-16 22:42:06 發布 一、組…

滲透測試常用指令

互聯網設備的開放信息查詢網站&#xff1a; https://fofa.info/ https://www.zoomeye.org/ https://quake.360.net/quake/#/index https://x.threatbook.com/v5/mapping https://hunter.qianxin.com/ 目錄 一、網絡探測與掃描 traceroute whatweb ping fping nc n…

51單片機串行通信的設計原理有哪些?

51單片機是指由美國INTEL公司生產的一系列單片機的總稱&#xff0c;這一系列單片機包括了許多品種&#xff0c;如8031&#xff0c;8051&#xff0c;8751&#xff0c;8032&#xff0c;8052&#xff0c;8752等&#xff0c;其中8051是最早最典型的產品&#xff0c;該系列其它單片機…

設計模式十四:適配器模式(Adapter Pattern)

適配器模式&#xff08;Adapter Pattern&#xff09;是一種結構型設計模式&#xff0c;用于將一個類的接口轉換成客戶端期望的另一個接口&#xff0c;使原本不兼容的類可以一起工作。適配器模式的類型類適配器&#xff08;通過多重繼承實現&#xff09;對象適配器&#xff08;通…

力扣經典算法篇-38-組合(回溯算法)

1、題干 給定兩個整數 n 和 k&#xff0c;返回范圍 [1, n] 中所有可能的 k 個數的組合。 你可以按 任何順序 返回答案。 示例 1&#xff1a; 輸入&#xff1a;n 4, k 2 輸出&#xff1a; [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ] 示例 2&#xff1a; 輸入&#xff1a;…

多人命題系統

目 錄 摘 要 Abstract 1 系統概述 1.1 概述 1.2課題意義 1.3 主要內容 2 系統開發環境 2. 1 JAVA簡介 2. .2 B/S架構 2.3 SSM三大框架 2.4訪問數據庫實現方法 2.5 系統對MySQL數據庫的兩種連接方式 3 需求分析 3.1技術可行性&#xff1a;技術背景…

UDP_千兆光通信(四)Tri Mode Ethernet MAC ip核

Tri Mode Ethernet MAC ip核使用與例程分析 一、 Tri Mode Ethernet MAC ip核功能 二、 Tri Mode Ethernet MAC ip核配置 數據傳輸速率 主要設置接口 幀濾波功能選擇,以及流控選擇 三、 Tri Mode Ethernet MAC ip核使用 3.1 ip核接口 3.2 ip核接口說明 3.2.1 tx_ifg_delay 3.2…

Linux網絡:多路轉接 epoll

Linux網絡&#xff1a;多路轉接 epoll一、epoll三個接口函數1、epoll_create2、epoll_ctl3、epoll_wait二、epoll的工作原理三、epoll的echo_server1、EpollServer類2、構造函數3、事件循環4、事件派發5、事件處理6、測試四、LT和ET模式1、LT2、ET五、項目代碼一、epoll三個接口…

uniapp 微信小程序 列表點擊分享 不同的信息

<button open-type"share" plain class"item share" click.stop"shareFn(item)"><text>分享</text> </button>import {onShareAppMessage} from dcloudio/uni-applet shareObj ref({})// 將點擊后的分享設置信息 關鍵…

C# 匿名方法詳解

C# 匿名方法詳解 引言 在C#編程語言中,匿名方法是使用Lambda表達式創建的沒有名稱的方法。它們在LINQ查詢、事件處理和其他場合中非常有用。本文將詳細介紹C#匿名方法的基本概念、語法、使用場景以及優勢。 匿名方法的概念 匿名方法是一種無需顯式定義名稱的方法。在C#中,…

SD卡簡介與驅動開發

基本概念 存儲卡有很多種類&#xff0c;CF卡、記憶棒、SD卡、XD卡、MMC卡、MS卡、TF卡、MicroSD卡等。平時最常見的有SD卡和MicroSD卡兩種&#xff0c; SD卡和MicroSD只是兩張卡的大小不同&#xff0c;規格版本是完全相同的&#xff0c;均由SD卡協會推出。 SD卡有不少規范&…

大數據平臺數倉數湖hive之拉鏈表高效實現

對于緩慢變化的維度表&#xff0c;如客戶表&#xff0c;員工表&#xff0c;為了不丟失歷史數據&#xff0c;又不至于太浪費存儲空間&#xff0c;我們采用拉鏈表實現。 實現過程如下&#xff1a; 1、采集初始數據&#xff1a; 1.1 從mysql導出數據到hdfs /data/dolphinschedu…