HashMap----源碼解讀

源碼分析:
public class HashMap<K,V> extends AbstractMap<K,V>implements Map<K,V>, Cloneable, Serializable

在類的開頭聲明了幾個常量,以下是較為重要的:

/*** 定義初始容量大小為16*/
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; // aka 16 
?
/*** 定義最大容量為2^30*/
static final int MAXIMUM_CAPACITY = 1 << 30;
?
/*** 定義加載因子,與數組實時容量相乘會得到一個擴容閾值(threshold),當到達這個閾值時,將會進行擴容。*/
static final float DEFAULT_LOAD_FACTOR = 0.75f;
?
/*** 當鏈表元素增加到8時,轉化為紅黑樹提升查找效率
*/
?
static final int TREEIFY_THRESHOLD = 8;
?
/*** 當紅黑樹元素減少到6時,退化為鏈表
*/
static final int UNTREEIFY_THRESHOLD = 6;
?
/*** 只有當哈希表的總容量至少為64時,才可能將鏈表轉換為紅黑樹。
*/
static final int MIN_TREEIFY_CAPACITY = 64;

以下是定義的一些成員變量:

/*** 這是HashMap存儲數據的哈希表,它是一個數組,每個元素是一個鏈表的頭節點或者紅黑樹的*/
transient Node<K,V>[] table;
?
/*** 這是一個緩存,用于存儲HashMap中所有鍵值對(Entry)的集合視圖。*/
transient Set<Map.Entry<K,V>> entrySet;
?
/*** 這個字段表示HashMap中鍵值對的總數。*/
transient int size;
?
/*** 這個字段記錄了HashMap結構上被修改的次數,包括添加、刪除操作,或者重新哈希(rehash)等。* 它用于實現快速失敗(fail-fast)機制,當HashMap在迭代過程中被修改時,會拋出*/
transient int modCount;
?
/**
這個字段表示HashMap能夠容納的最大元素數量,達到這個數量時,HashMap會進行擴容(resize)。它等于數組的容量乘以加載因子(load factor)。如果哈希表還沒有被分配,這個字段可以表示初始數組容量或0,0代表使用默認的初始容量。*/
int threshold;
?
/**
這個字段是HashMap的加載因子,它決定了HashMap何時進行擴容操作。加載因子是HashMap中元素數量與數組長度的比例。當HashMap中的元素數量超過了capacity * loadFactor時,HashMap會進行擴容。默認的加載因子是0.75,這是一個空間和時間成本之間的折中。*/
final float loadFactor;

對于鏈表元素,會將其存儲在一個叫Node的內部類中,對于紅黑樹元素,會被存儲與TreeNode內部類中:

static class Node<K,V> implements Map.Entry<K,V> {final int hash;//hash值final K key;//鍵V value;//值Node<K,V> next;//指向下一個元素...
}
static final class TreeNode<K,V> extends LinkedHashMap.Entry<K,V> {TreeNode<K,V> parent;// 父節點TreeNode<K,V> left;//左子樹TreeNode<K,V> right;//右子樹TreeNode<K,V> prev;// 這是一個指向當前節點的前一個節點的引用。這個字段主要用于在刪除節點時,能夠從雙向鏈表中移除當前節點。由于HashMap中的紅黑樹節點也是雙向鏈表的一部分,所以這個字段是必要的。boolean red;//是否轉為紅色...
}

在初始化的時候,我們查看其中的一個無參構造:

public HashMap() {this.loadFactor = DEFAULT_LOAD_FACTOR; // 在調用無參構造,只對加載因子做了初始化,其他都沒有初始化。
}

當我們進行插入元素時,我們會調用put方法進行添加元素,傳入鍵值對:

public V put(K key, V value) {return putVal(hash(key), key, value, false, true);//依次參數是// 1.對鍵進行hash(計算鍵的哈希值以確定它應該存儲在哪個桶中)// 2.鍵// 3.值// 4.是否保留(false時重復會進行覆蓋)// 5.這個布爾值參數用于LinkedHashMap,它指示在插入后是否需要執行額外的操作。在HashMap中,這個參數通常被忽略,因為它不是用來控制標準HashMap行為的。在LinkedHashMap中,這個參數用于確定是否在插入后移除最舊的條目
}

接著我們進入putVal方法查看:

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,boolean evict) {//由于table是成員變量放在堆中,而方法在棧中,所以定義一個局部變量(同樣存在于棧中)提高效率Node<K,V>[] tab; //指向當前數組位置Node<K,V> p; //n為數組容量,i為以hash值與數組長度運算得到的插入位置索引(桶索引)int n, i;//對tab進行賦值并且判斷是否為空,其實就是對我們的數組判斷是否為空(還沒初始化),調用resize函數進行初始化:if ((tab = table) == null || (n = tab.length) == 0)n = (tab = resize()).length;//判斷在數組中,該位置是否為空,為空直接插入if ((p = tab[i = (n - 1) & hash]) == null)//將我們的元素插入到數組中。tab[i] = newNode(hash, key, value, null);//不為空else {Node<K,V> e; K k;//判斷是否重復if (p.hash == hash &&((k = p.key) == key || (key != null && key.equals(k))))//重復則將存在的元素賦值給e,后續可以用來更新該節點的值。e = p;//如果存在的元素的類型是紅黑樹節點else if (p instanceof TreeNode)e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);//在原來元素的基礎上進行鏈表插入的操作else {//這里開始了一個無限循環,binCount用于記錄當前桶中的節點數量。循環將遍歷鏈表中的節點,直到找到合適的插入位置。for (int binCount = 0; ; ++binCount) {
//在循環內部,首先檢查當前節點p的下一個節點e是否為null。如果是null,說明已經到達鏈表的末尾,可以在這里插入新的節點。if ((e = p.next) == null) {//在存在元素上使用尾插法進行插入新元素p.next = newNode(hash, key, value, null);//達到樹化閾值,對當前哈希桶轉換為紅黑樹if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1sttreeifyBin(tab, hash);//插入超過即breakbreak;}
//在遍歷鏈表的過程中,如果找到了一個具有相同哈希值和鍵的節點,這意味著找到了一個已經存在的鍵。
//如果鍵相等(通過==比較或者equals方法),循環會通過break終止。if (e.hash == hash &&((k = e.key) == key || (key != null && key.equals(k))))break;//如果沒有找到相等的鍵,或者還沒有到達鏈表末尾,p會更新為下一個節點e,繼續循環。p = e;}}//經過上訴操作之后,如果e不為null則說明已經找到了重復元素if (e != null) { // existing mapping for keyV oldValue = e.value;//判斷是否要進行覆蓋,因為重復時e指向的是重復元素,此時進行重復元素value的覆蓋if (!onlyIfAbsent || oldValue == null)e.value = value;//這個方法在HashMap類中是空的,用于LinkedHashMap的位置調整,因為有重復元素覆蓋則涉及一個插入順序打亂afterNodeAccess(e);//返回舊值return oldValue;}}++modCount;//大于閾值則調用resize準備擴容if (++size > threshold)resize();//它在節點被插入后調用。這個方法在HashMap類中是空的,但在LinkedHashMap中會被覆蓋以維護節點的插入順序。afterNodeInsertion(evict);//正常插入返回nullreturn null;
}

在resize方法中,由于我們的容量等于零,所以他會執行其中的:

{ ? ? ? ? ? ? ? newCap = DEFAULT_INITIAL_CAPACITY;//給我們的容量賦值默認容量16newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);//給我們的閾值賦值為容量乘以加載因子
}
threshold = newThr;//賦值給成員變量
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];//此時才開始初始化存放鏈表或者紅黑樹的數組table = newTab;//將其賦值給成員變量table
...
return newTab;最后將我們的新數組進行返回。

以上是其中的一種情況,在resize中有三種情況,以下是其他兩種:

//當舊容量大于0,此時調用到resize則說明需要進行擴容操作
if (oldCap > 0) {//判斷舊容量有沒有超過最大,超過則設置閾值為Int最大,表示再也不會擴容了。if (oldCap >= MAXIMUM_CAPACITY) {threshold = Integer.MAX_VALUE;return oldTab;}//開始擴容,讓新容量左移一位即為2倍操作,并進行判斷新容量有沒有超過閾值。else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&oldCap >= DEFAULT_INITIAL_CAPACITY)//如果以上判斷通過則將新閾值變為舊閾值的兩倍newThr = oldThr << 1; // double threshold
}
//當舊閾值大于零且不滿足舊容量大于零(以上情況),則說明在創建hashMap時進行了初始化容量,當插入元素時會調用resize來到這個if
else if (oldThr > 0) // initial capacity was placed in thresholdnewCap = oldThr;

當擴容之后我們會對對應的成員變量進行賦值,并且讓舊數組的元素拷貝到新數組中去:

//閾值更新,即下一次擴容時機
threshold = newThr;
@SuppressWarnings({"rawtypes","unchecked"})
//創建新數組
Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
//將成員變量table賦值新數組
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;//樹結構節點else if (e instanceof TreeNode)((TreeNode<K,V>)e).split(this, newTab, j, oldCap);//鏈表結構else { // preserve orderNode<K,V> loHead = null, loTail = null;Node<K,V> hiHead = null, hiTail = null;Node<K,V> next;//低位:落在新容量的(0,舊容量大小)區域//高位:落在新容量的(舊容量大小,兩倍舊容量)區域//先使用其hash值判斷它在高位區還是低位區,hash與舊容量相與等于零則說明其在低位。//判斷后,就可以把j索引下的一整條鏈表進行復制//復制過程就是自己造一條新鏈表,如落在低位時://先使用lohead將頭節點保存,其次用lotail.next在循環中將整條鏈表進行連接//整條鏈表復制好了,即走完了dowhile,此時再一次判斷是高位還是低位(判斷高或低有沒有為空)不為空則為高或低位。//如果是低位直接將頭節點插入到新容量數組的j索引處,如果是高位則將頭節點插入在新容量(j+舊容量大小)索引處do {next = e.next;if ((e.hash & oldCap) == 0) {if (loTail == null)loHead = e;elseloTail.next = e;loTail = e;}else {if (hiTail == null)hiHead = e;elsehiTail.next = e;hiTail = e;}} while ((e = next) != null);if (loTail != null) {loTail.next = null;newTab[j] = loHead;}if (hiTail != null) {hiTail.next = null;newTab[j + oldCap] = hiHead;}}}}
}
return newTab;

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

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

相關文章

探索【Python面向對象】編程:新時代的高級編程范式詳解

目錄 1. 面向對象編程概念&#xff08;OOP&#xff09; 1.1 什么是類和對象&#xff1f; 1.2 類的定義 1.3 類和對象的關系 1.4 小李的理解 2. 抽象 2.1 抽象的概念 2.2 抽象類和方法 2.3 小李的理解 3. 類和實例 3.1 類的定義和實例化 3.2 類的屬性和方法 3.3 小…

如何使用Python在企業微信中發送測試結果?操作看這里!

在日常的自動化測試工作中&#xff0c;一般會需要把測試結果同步到工作群里&#xff0c;方便信息同步。那么我們今天就使用企業微信和Pythonrequests庫來演示一下具體如何操作吧&#xff01; 01 準備 開始之前&#xff0c;我們應該確保已經安裝了python環境&#xff0c;并且要…

DNS知識點

??打牌 : da pai ge的個人主頁 ???個人專欄 : da pai ge的博客專欄 ??寶劍鋒從磨礪出,梅花香自苦寒來 ? 目錄 一、DNS概念 二 hosts 文件 三 DNS優缺點 三 客戶端域名解析順序(優先級)…

8.9分王者“水刊”!1區IEEE-Trans,國人主編坐鎮!發文量2倍增長,擴刊趨勢明顯!

關注GZH【歐亞科睿學術】&#xff0c;第一時間了解最新期刊動態&#xff01; 本期&#xff0c;小編給大家推薦的是一本IEEE旗下王者“水刊”。該期刊目前處于擴刊狀態&#xff0c;接收跨學科領域&#xff0c;領域認可度高&#xff0c;還可選擇非OA模式無需版面費&#xff0c;是…

PPTP、L2TP、IPSec、IPS 有什么區別?

隨著互聯網的發展&#xff0c;保護網絡通信的安全越來越重要。PPTP、L2TP、IPSec、IPS是常見的網絡安全協議和技術&#xff0c;在保護網絡通信安全方面發揮著不同的作用和特點。下面介紹PPTP、L2TP、IPSec、IPS之間的區別。 點對點隧道協議&#xff08;PPTP&#xff09;是一種用…

對素數的一種新理解

素數是除了1和它自身沒有其它因數的自然數&#xff08;不包括1&#xff09;。素數被認為是自然數的基礎&#xff0c;就像自然界的原子一樣&#xff0c;可以通過若干個素數的乘積表示所有大于1的自然數&#xff0c;而且這種表示是唯一的&#xff08;不考慮素數的順序&#xff09…

HTTP協議分析/burp/goby/xray

一、HTTP簡介 HTTP(超文本傳輸協議)是今天所有web應用程序使用的通信協議。最初&#xff0c;HTTP只是一個為獲取基于文本的靜態資源而開發的簡單協議&#xff0c;后來人們以名種形式擴展和利用它.使其能夠支持如今常見的復雜分布式應用程序。HTTP使用一種用于消息的模型:客戶端…

Golang異常處理機制

go語言使用error來處理錯誤&#xff0c;用panic和recover來處理異常 error go語言的錯誤處理有兩個發展階段&#xff0c;以go1.13版本為分水嶺&#xff0c;在1.13版本之前&#xff0c;標準庫對error的支持非常有限&#xff0c;僅有errors.New()和fmt.Errorf()兩個函數來構造e…

javaweb中的請求與響應--基于postman工具的應用(附帶postman的詳細安裝步驟)

一、前言 后端的第一天感覺難度就上來了&#xff0c;可能是基礎太過薄弱了吧。目前看視頻已經有點跟不上了&#xff0c;果然15天想要拿下還是太勉強了點。30天還差不多。不知道讀者們有沒有好好的去學這方面的知識&#xff0c;沒有什么是學不會的&#xff0c;關鍵是堅持。 Po…

幾個小創新模型,KAN組合網絡(LSTM、GRU、Transformer)回歸預測,python預測全家桶再更新!...

截止到本期&#xff0c;一共發了9篇關于機器學習預測全家桶Python代碼的文章。參考往期文章如下&#xff1a; 1.終于來了&#xff01;python機器學習預測全家桶 2.機器學習預測全家桶-Python&#xff0c;一次性搞定多/單特征輸入&#xff0c;多/單步預測&#xff01;最強模板&a…

蘿卜快跑的狠活

蘿卜快跑作為百度旗下的自動駕駛出行服務平臺&#xff0c;在科技應用上展現了多項領先的技術。以下是蘿卜快跑采用的一些主要科技“狠活”&#xff1a; 自動駕駛技術&#xff1a; 蘿卜快跑主要使用了百度Apollo的L4級自動駕駛技術&#xff0c;該技術能夠應對海量的城市道路場景…

C++:重定義

派生類和基類的同名成員問題 派生類中再實現一個基類中的方法會怎樣 (1)代碼實驗&#xff1a;派生類和基類中各自實現一個內容不同但函數原型完全相同的方法&#xff0c;會怎么樣 (2)結論&#xff1a;基類對象調用的是基類的方法&#xff0c;派生類對象調用執行的是派生類中重…

進程調度篇

在操作系統的廣闊領域中&#xff0c;進程調度是其中一個至關重要的環節。它如同操作系統的“交通警察”&#xff0c;負責在多個等待CPU執行的進程間進行高效、公平的分配。本文將帶您了解進程調度的基本概念、重要性、常用算法…… 1. 進程調度的基本概念 1.1 進程調度的定義 …

【FreeRTOS】freeRTOS的Tmr Svc任務優先級配置

1、Tmr Svc是個FreeRTOS的軟件定時器任務&#xff0c;他可以收集各任務的狀態 2、他的優先級可以通過宏 configTIMER_TASK_PRIORITY 來配置&#xff0c;默認是2 3、修改為31后&#xff0c;程序總是啟動不了&#xff0c; 4、后面才發現原來FreeRTOS的默認最大優先級號配置的是…

工具指南 - jenkins

一、接入SonarQube 掃描代碼 SonarQube是一個用于管理代碼質量的開放平臺&#xff0c;可以快速的定位代碼中潛在的或者明顯的錯誤。 1.1 源碼管理 如果源碼托管在SVN&#xff0c;需要進行Subversion配置&#xff1a; Repository URL&#xff1a;源碼地址&#xff0c;比如https:…

一鍵優雅為Ubuntu20.04服務器掛載新磁盤

itopen組織1、提供OpenHarmony優雅實用的小工具2、手把手適配riscv qemu linux的三方庫移植3、未來計劃riscv qemu ohos的三方庫移植 小程序開發4、一切擁抱開源&#xff0c;擁抱國產化 一、小于2T磁盤掛載方式 1.1 安裝磁盤到電腦后啟動系統 1.2 查找未分區的磁盤 打…

ios swift5 藍牙廣播出數據

WARNING: The advertisement key ‘Manufacturer Data’ is not allowed WARNING: The advertisement key ‘Service Data’ is not allowed manager?.startAdvertising([CBAdvertisementDataServiceUUIDsKey : [myService?.uuid], CBAdvertisementDataLocalNameKey : "…

鴻蒙Navigation的頁面跳轉官方代碼

星河版本 文章部分代碼來源于官方 文章部分代碼來源于官方只是自己改了容易理解 與API4不同的Navigation 新版本使用的思路是 1、創建頁面棧 pageInfos: NavPathStack new NavPathStack();2、resources/base/profile創建 router_map.json 文件 {"routerMap":…

數電設計提問求幫助,出租車計費器。

&#x1f3c6;本文收錄于《CSDN問答解惑-》專欄&#xff0c;主要記錄項目實戰過程中的Bug之前因后果及提供真實有效的解決方案&#xff0c;希望能夠助你一臂之力&#xff0c;幫你早日登頂實現財富自由&#x1f680;&#xff1b;同時&#xff0c;歡迎大家關注&&收藏&…

Autosar診斷實戰系列28-2E寫DID Pending期間偶發回NRC0x13問題排查

本文框架 前言1.問題描述2.問題復現3.問題分析問題1:為何在2E Pending期間會發送功能尋址的10 01回NRC13?問題2:在ECU Pending期間收到功能尋址10 01,MCU需要如何處理?問題3:DcmDslConnection是如何定義的?問題4:功能尋址于物理尋址是否對應不同的DcmDslConnection?問…