HashMap的get與put流程源碼深度解析

目錄

一、HashMap基礎結構

二、put操作流程分析

put操作關鍵步驟總結

三、get操作流程分析

get操作關鍵步驟總結

四、延伸

1.hash()方法

2. 擴容

resize()方法的主要邏輯:

Java 8中對擴容的優化:

3. 轉向紅黑樹的條件


HashMap作為Java集合框架中最重要且最常用的數據結構之一,其內部實現機制值得深入探究。本文將從源碼角度分析HashMap的get和put操作流程,基于Java 8的實現進行講解。

一、HashMap基礎結構

在講解get和put之前,我們先了解HashMap的核心結構:

// HashMap的主要成員變量
transient Node<K,V>[] table; // 存儲元素的數組
transient int size;         // 實際存儲的鍵值對數量
int threshold;              // 擴容閾值 (capacity * loadFactor)
final float loadFactor;     // 負載因子(默認0.75)

HashMap采用"數組+鏈表+紅黑樹"的混合存儲結構:

  • 數組(table)是主體

  • 當哈希沖突時,使用鏈表法解決

  • 當鏈表長度超過8且數組長度≥64時,鏈表轉為紅黑樹

二、put操作流程分析

put操作是HashMap的核心功能之一,其中主要方法為:

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);
}

核心邏輯putVal方法:

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:table為空則初始化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已存在,直接覆蓋valueif (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:寫入valueif (e != null) { // existing mapping for keyV oldValue = e.value;if (!onlyIfAbsent || oldValue == null)e.value = value;afterNodeAccess(e);return oldValue;}}// 步驟7:超過閾值則擴容++modCount;if (++size > threshold)resize();afterNodeInsertion(evict);return null;
}

put操作關鍵步驟總結

  1. 計算哈希值:通過hash()方法計算key的哈希值

  2. 初始化檢查:如果table為空,則調用resize()初始化

  3. 定位桶位置:通過(n-1) & hash計算數組下標(等價于 hash % n,但效率更高--位運算比取模快

  4. 處理沖突

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

    • 如果key已存在,則覆蓋value

    • 如果是樹節點,調用紅黑樹的插入方法

    • 如果是鏈表,遍歷鏈表并在尾部插入

  5. 樹化檢查:鏈表長度≥8時可能轉為紅黑樹

  6. 擴容檢查:size超過threshold則擴容

三、get操作流程分析

首先來看get的實現方法:

public V get(Object key) {Node<K,V> e;return (e = getNode(hash(key), key)) == null ? null : e.value;
}

其核心實現getNode方法解析:

final Node<K,V> getNode(int hash, Object key) {Node<K,V>[] tab; Node<K,V> first, e; int n; K k;// 步驟1:table不為空且長度>0且對應位置有節點if ((tab = table) != null && (n = tab.length) > 0 &&(first = tab[(n - 1) & hash]) != null) {// 步驟2:檢查第一個節點if (first.hash == hash && // always check first node((k = first.key) == key || (key != null && key.equals(k))))return first;// 步驟3:遍歷后續節點if ((e = first.next) != null) {// 步驟4:如果是樹節點if (first instanceof TreeNode)return ((TreeNode<K,V>)first).getTreeNode(hash, key);// 步驟5:遍歷鏈表do {if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))return e;} while ((e = e.next) != null);}}return null;
}

get操作關鍵步驟總結

  1. 計算哈希值:與put相同的hash()方法

  2. 定位桶位置:同樣的(n-1) & hash計算方式

  3. 檢查首節點:先比較hash,再比較key

  4. 處理沖突

    • 如果是樹節點,調用紅黑樹的查找方法

    • 如果是鏈表,遍歷查找

  5. 返回結果:找到返回value,未找到返回null

四、延伸

1.hash()方法

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

可以看見代碼中將高16位與低16位異或,這么做可以實現:

  • 減少哈希沖突

  • 充分利用高位的哈希信息

  • 使哈希分布更加均勻

2. 擴容

resize()方法的主要邏輯:

  1. 計算新容量和新閾值

  2. 創建新數組

  3. 將舊數組元素重新分配到新數組(rehash)

Java 8中對擴容的優化:

  • 無需重新計算hash,通過(e.hash & oldCap) == 0判斷位置

  • 元素要么在原位置,要么在原位置+oldCap的位置

3. 轉向紅黑樹的條件

鏈表轉為紅黑樹的兩個條件:

  1. 鏈表長度 ≥ TREEIFY_THRESHOLD(8)

  2. 數組長度 ≥ MIN_TREEIFY_CAPACITY(64)

【注】上面表示默認情況下是鏈表長度-8,且數組長度-64

否則會優先進行擴容而不是轉向紅黑樹。

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

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

相關文章

初識Neo4j之圖數據庫(二)

目錄 一、圖數據庫如何工作 二、為什么使用圖數據庫 Neo4j 圖數據庫以節點、關系和屬性的形式存儲數據&#xff0c;而不是用表或文檔進行數據存儲。這意味著用戶可以像在白板上畫草圖那樣來組織數據。而且&#xff0c;由于圖數據庫不受限于預先定義的數據模型&#xff0c;因此…

Python 中 ffmpeg-python 庫的詳細使用

文章目錄 一、ffmpeg-python庫概述1.1 ffmpeg-python庫介紹1.2 安裝1.3 優勢1.4 常用場景二、基本使用2.1 視頻信息獲取2.2 視頻轉碼三、視頻處理3.1 視頻裁剪3.2 視頻縮放3.3 視頻旋轉四、音頻處理4.1 提取音頻4.2 音頻混合五、高級使用5.1 添加水印5.2 視頻濾鏡5.3 視頻合成5…

JAVA策略模式demo【設計模式系列】

策略模式用在統一的入口&#xff0c;但需要根據某個類型判斷后續執行邏輯&#xff0c;例如我最近遇到的場景&#xff1a;我需要對接一個設備&#xff0c;前端請求我這邊&#xff0c;我再去和設備交互&#xff0c;但設備種類很多&#xff0c;各自有自己的接入規則&#xff01;傳…

mysql索引:索引應該選擇哪種數據結構 B+樹 MySQL中的頁 頁主體 頁目錄 索引分類

索引是什么?為什么要使用索引? 以前學數據結構時學了ArrayList,我們可以往里面存放數據 但是有問題,也就是說當程序重啟或是電腦關機之后,數據就沒有了,為什么? 因為他的數據是保存在內存中的 數據庫把數據保存在磁盤中,就可以完成對數據的持久化內存與外存的區別 內存&…

SpringBoot靜態資源與緩存配置全解析

springboot中靜態資源classpath就是resource文件夾下歡迎頁規則項目啟動默認去找靜態資源下的index.html頁面 默認訪問該頁面favicon原則在靜態資源目錄下尋找favicon.ico緩存實驗在請求中使用Cache-Control 時&#xff0c;它可選的值有&#xff1a;在響應中使用Cache-Control …

基于 Python Django 和 Spark 的電力能耗數據分析系統設計與實現7000字論文實現

摘要隨著能源問題日益突出&#xff0c;電力能耗數據分析對于提高能源利用效率、降低能源消耗具有重要意義。本文設計并實現了一個基于 Python Django 和 Spark 的電力能耗數據分析系統。系統采用前后端分離架構&#xff0c;前端使用 Django 框架實現用戶界面&#xff0c;后端使…

elementUI vue2 前端表格table數據導出(二)

為啥前端導出不在贅述了&#xff0c;不然讀者也難看到這篇文章。第一步&#xff1a;安裝依賴npm install vue-json-excel第二步&#xff1a;引用依賴配置// 導出Excel文件組件 import JsonExcel from vue-json-excel; Vue.component(downloadExcel, JsonExcel)第三步&#xff1…

RabbitMQ 4.1.1-Local random exchange體驗

Local Random Exchange 一種 RabbitMQ 4.0 引入的新型交換機&#xff0c;主要是為 request-reply&#xff08;RPC&#xff09;場景 設計的。 使用這種交換機時&#xff0c;消息只會被路由到本地節點上的隊列&#xff0c;可以確保極低的消息發布延遲。如果有多個本地隊列綁定到該…

中山排氣歧管批量自動化智能化3D尺寸測量及cav檢測分析

當前制造業快速發展&#xff0c;傳統測量方法正面臨嚴峻挑戰。生產規模的持續擴張使得現有測量手段逐漸暴露出效率不足的問題&#xff0c;這種技術滯后性正在直接影響企業的整體生產效率。具體表現為測量速度跟不上生產節拍&#xff0c;精度要求難以達標&#xff0c;最終導致生…

Debian 11 Bullseye 在線安裝docker

首先移除所有錯誤的 Docker 軟件源&#xff1a;sudo rm -f /etc/apt/sources.list.d/docker*安裝必要依賴sudo apt update sudo apt install -y ca-certificates curl gnupg添加 Docker 官方 GPG 密鑰&#xff08;使用國內鏡像&#xff09;&#xff1a;curl -fsSL https://mirr…

Spring Boot 項目中多數據源配置使用場景

在 Spring Boot 中配置多數據源是一個非常常見的需求&#xff0c;主要用于以下場景&#xff1a; 讀寫分離&#xff1a;一個主數據庫&#xff08;Master&#xff09;負責寫操作&#xff0c;一個或多個從數據庫&#xff08;Slave&#xff09;負責讀操作&#xff0c;以提高性能和可…

FAAC 在海思平臺使用得到aac實時音頻流

FAAC 在海思平臺使用得到aac實時音頻流 使用 FAAC將音頻 pcm轉為 aac 主要參見這篇博客 FAAC 在君正平臺使用得到aac實時音頻流_君正 x2600 音頻-CSDN博客

javascript函數參數類似python函數參數星號*解耦數組

序言通常情況下&#xff0c;我們很可能不清楚參數有多少&#xff0c;這個時候用的都是數組。但是使用數組和單個元素&#xff0c;從內心情感來說&#xff0c;它們是兩種維度&#xff0c;為了讓參數成為一個數組&#xff0c;把單個輸入的參數強加一個數組的外殼&#xff0c;并不…

C語言基礎(1)

1.編譯器的選擇 我們的c語言是一門&#xff0c;我們寫的c語言代碼是文本文件(存放在.c為后綴的文件中)&#xff0c;文本文件本身無法被執行&#xff0c;必須通過編譯器的編譯和鏈接器的鏈接&#xff0c;生成可執行的二進制文件&#xff0c;才能夠被執行注意&#xff1a; 每個源…

Rust賦能美團云原生DevOps實踐

Rust 云原生 DevOps 實踐 在云原生環境中,Rust 的高性能與安全性使其成為構建微服務和基礎設施工具的理想選擇。Docker 作為容器化標準工具,結合 Rust 的跨平臺特性,可高效實現持續集成與部署(CI/CD)。 構建優化的 Rust Docker 鏡像 多階段構建是 Rust 項目容器化的關鍵…

計算機網絡實驗——配置ACL

ACL基礎一、實驗目的1. 配置H3C路由器基本ACL。二、實驗要求1. 熟練掌握網絡配置能力。2. 熟練掌握ACL基本配置。三、實驗步驟&#xff08;1&#xff09;使用reset saved-configuration命令和reboot命令&#xff0c;重置路由器原有配置&#xff0c;如圖1所示。圖 1&#xff08;…

在本地部署mcp服務器實現自然語言操作mysql數據庫,輕松實現數據表的增~ 刪~ 改~ 查~

1.將寫好的mcp_server代碼放在本地任意盤&#xff01; import asyncio import logging import os import sys from mysql.connector import connect, Error from mcp.server import Server from mcp.types import Resource, Tool, TextContent from pydantic import AnyUrl# Co…

2025快手創作者中心發布視頻python實現

難度還行&#xff0c;只有一個__NS_sig3加密&#xff0c;流程麻煩點cookies_list cookie.split("; ")cookie_dict {}# 遍歷每個 Cookie&#xff0c;根據等號將鍵值對拆分并添加到字典中for cookie in cookies_list:key_value cookie.split("")if len(ke…

Android 組件內核

文章目錄什么是binder1. 什么是Binder&#xff1f;2. Binder架構組成3. 工作原理與通信流程1&#xff09;服務注冊2&#xff09;服務查詢3&#xff09;通信過程4&#xff09;核心數據結構4. 關鍵技術點5. 常見面試考點1&#xff09;Binder與傳統IPC&#xff08;Socket、管道、共…

java類加載機制:Tomcat的類加載機制

Tomcat類加載機制深度解析&#xff1a;打破雙親委派的Web容器實現 Tomcat作為Java Web容器&#xff0c;其類加載機制為滿足Web應用的隔離性、熱部署和兼容性需求&#xff0c;對標準Java類加載機制進行了定制化擴展&#xff0c;核心是打破雙親委派模型并引入多層級類加載器。以下…