主頁 >  其他 > 金三銀四助力面試-手把手輕松讀懂HashMap原始碼

金三銀四助力面試-手把手輕松讀懂HashMap原始碼

2021-02-19 14:50:53 其他

手把手輕松讀懂HashMap原始碼

  • 前言
  • HashMap 原始碼分析
    • HashMap 中的資料存盤
    • 為什么建議初始化 HashMap 時指定大小
      • 負載因子
      • HashMap 默認容量
    • HashMap 最大容量是多少
    • 為什么 HashMap 容量大小要為 2 的 N 次冪
    • 談談 HashMap 中的哈希運算
    • put 元素流程
    • 擴容
      • 擴容之后原有資料怎么處理
        • 鏈表資料處理
  • 總結

前言

HashMap 對每一個學習 Java 的人來說熟悉的不能再熟悉了,然而就是這么一個熟悉的東西,真正深入到原始碼層面卻有許多值的學習和思考的地方,現在就讓我們一起來探索一下 HashMap 的原始碼,

HashMap 原始碼分析

HashMap 基于哈希表,且實作了 Map 介面的一種 key-value 鍵值對存盤資料結構,其中的 keyvalue 均允許 null 值,在 HashMap 中,不保證順序,執行緒也不安全,

HashMap 中的資料存盤

HashMap 中,每次 put 操作都會對 key 進行哈希運算,根據哈希運算的結果然后再經過位運算得到一個指定范圍內的下標,這個下標就是當前 key 值存放的位置,既然是哈希運算,那么肯定就會有哈希沖突,對此在 jdk1.7 及其之前的版本,每個下標存放的是一個鏈表,而在 jdk1.8 版本及其之后的版本,則對此進行了優化,當鏈表長度大于 8 時,會轉化為紅黑樹存盤,當紅黑樹中元素從大于等于 8 降低為 小于等于 6 時,又會從紅黑樹重新退化為鏈表進行存盤,

下圖就是 jdk1.8 中一個 HashMap 的存盤結構示意圖(每一個下標的位置稱之為 ):

在這里插入圖片描述

HashMap 中是通過陣列 + 鏈表 + 紅黑樹來實作資料的存盤(jdk1.8),而在更早的版本中則僅僅采用了陣列 + 鏈表的方式來進行存盤,

為什么建議初始化 HashMap 時指定大小

HashMap 初始化的時候,我們通常都會建議預估一下可能大小,然后在構造 HashMap 物件的時候指定容量,這是為什么呢?要回答這個問題就讓我們看一下 HashMap 是如何初始化的,

下圖就是當我們不指定任何引數時創建的 HashMap 物件:

在這里插入圖片描述

負載因子

可以看到有一個默認的 DEFAULT_LOAD_FACTOR(負載因子),這個值默認是 0.75,負載因子的作用就是當 HashMap 中使用的容量達到總容量大小的 0.75 時,就會進行自動擴容,然后從上面的原始碼可以看到,當我們不指定大小時,HashMap 并不會初始化一個容量,那么我們就可以大膽的猜測當我們呼叫 put 方法時肯定會有判斷當前 HashMap 是否被初始化,如果沒有初始化,就會先進行初始化,

HashMap 默認容量

put 方法中會判斷當前 HashMap 是否被初始化,如果沒有被初始化,則會呼叫 resize 方法進行初始化,resize 方法不僅僅用于初始化,也用于擴容,其中初始化部分主要是下圖中紅框部分:

在這里插入圖片描述

可以看到,初始化 HashMap 時,主要是初始化了 2 個變數:一個是 newCap,表示當前 HashMap 中有多少個桶,陣列的一個下標表示一個桶;一個是 newThr,主要是用于表示擴容的閾值,因為任何時候我們都不可能等到桶全部用完了才去擴容,所以要設定一個閾值,達到閾值后就開始擴容,這個閾值就是總容量乘以負載因子得到,

上面我們知道了默認的負載因子是 0.75,而默認的桶大小是 16,所以也就是當我們初始化 HashMap 而不指定大小時,當桶使用了 12 時就會自動擴容(如何擴容我們在后面分析),擴容就會涉及到舊資料遷移,比較消耗性能,所以在能預估出 HashMap 需要存盤的總元素時,我們建議是提前指定 HashMap 的容量大小,以避免擴容操作,

PS:需要注意的是,一般我們說 HashMap 中的容量都是指的有多少個桶,而每個桶能放多少個元素取決于記憶體,所以并不是說容量為 16 就只能存放 16key 值,

HashMap 最大容量是多少

當我們手動指定容量初始化 HashMap 時,總是會呼叫下面的方法進行初始化:

在這里插入圖片描述

看到 453 行,當我們指定的容量大于 MAXIMUM_CAPACITY 時,會被賦值為 MAXIMUM_CAPACITY,而這個 MAXIMUM_CAPACITY 又是多少呢?

在這里插入圖片描述

上圖中我們看到,MAXIMUM_CAPACITY 是 2 的 30 次方,而 int 的范圍是 2 的 31 次方減 1,這豈不是把范圍給縮小了嗎?看上面的注釋可以知道,這里要保證 HashMap 的容量是 2 的 N 次冪,而 int 型別的最大正數范圍是達不到 2 的 31 次冪的,所以就取了2 的 30 次冪,

我們再回到前面的帶有引數的構造器,最后呼叫了一個 tableSizeFor 方法,這個方法的作用就是調整 HashMap 的容量大小:

在這里插入圖片描述

這個方法如果大家不了解位運算,可能就會看不太明白這個到底是做什么操作,其實這個里面就做了一件事,那就是把我們傳進來的指定容量大小調整為 2 的 N 次冪,所以在最開始要把我們傳進去的容量減 1,就是為了統一調整,

我們來舉一個簡單的例子來解釋一下上面的方法,位運算就涉及到二進制,所以假如我們傳進來的容量是一個 5,那么轉化為二進制就是 0000 0000 0000 0000 0000 0000 0000 0101,這時候我們要保證這個數是 2 的 N 次冪,那么最簡單的辦法就是把我們當前二進制數的最左邊的 1 開始,一直到最右邊,所有的位都是 1,那么得到的結果就是得到對應的 2 的 N 次冪減 1,比如我們傳的 5 進來,要確保是 2 的 N 次冪,那么肯定是需要調整為 2 的 3 次 冪,即:8,這時候我么需要做的就是把后面的 3101 調整為 111 ,就可以得到 2 的 3 次冪減 1,最后的總容量再加上 1 就可以調整成為 2 的 N 次冪,

還是以 5 為例,無符號右移 1 位,得到 0000 0000 0000 0000 0000 0000 0000 0010,然后再與原值 0000 0000 0000 0000 0000 0000 0000 0101 執行 | 操作(只有兩邊同時為 0,結果才會為 0),就可以得到結果 0000 0000 0000 0000 0000 0000 0000 0111,也就是把第二位變成了 1,這時候不論再右移多少位,右移多少次,結果都不會變,保證了后三位為 1,而后面還要依次右移,是為了確保當一個數的第 31 位為 1 時,可以保證除了最高位之外的 31 位全部為 1

到這里,大家應該就會有疑問了,為什么要如此大費周章的來保證 HashMap 的容量,即桶的個數為 2 的 N 次冪呢?

為什么 HashMap 容量大小要為 2 的 N 次冪

之所以要確保 HashMap 的容量為 2 的 N 次冪的原因其實很簡單,就是為了盡可能避免哈希分布不均勻而導致每個桶中分布的資料不均勻,從而出現某些桶中元素過多,影響到查詢效率,

我們繼續看一下 put 方法,下圖中紅框部分就是計算下標位置的演算法,就是通過當前陣列(HashMap 底層是采用了一個 Node 陣列來存盤元素)的長度 - 1 再和 hash 值進行 & 運算得到的:

在這里插入圖片描述

& 運算的特點就是只有兩個數都是 1 得到的結果才是 1,那么假如 n-1 轉化為二進制中含有大量的 0,如 1000,那么這時候再和 hash 值去進行 & 運算,最多只有 1 這個位置是有效的,其余位置全部是 0,相當于無效,這時候發生哈希碰撞的概率會大大提升,而假如換成一個 1111 再和 hash 值進行 & 運算,那么這四位都有效參與了運算,大大降低了發生哈希碰撞的概率,這就是為什么最開始初始化的時候,會通過一系列的 | 運算來將其第一個 1 的位置之后所有元素全部修改為 1 的原因,

談談 HashMap 中的哈希運算

上面談到了計算一個 key 值最終落在哪個位置時用到了一個 hash 值,那么這個 hash 值是如何得到的呢?

下圖就是 HashMap 中計算 hash 值的方法:

在這里插入圖片描述

我們可以看到這個計算方法很特別,它并不僅僅只是簡單通過一個 hashCode 方法來得到,而是還同時將 hashCode 得到的結果無符號右移 16 位之后再進行異或運算,得到最終結果,

這么做的目的就是為了將高 16 位也參與運算,進一步避免哈希碰撞的發生,因為在 HashMap 中容量總是 2 的 N 次冪,所以如果僅僅只有低 16 位參與運算,那么有很大一部分情況其低 16 位都是 1,所以將高 16 位也參與運算可以一定程度避免哈希碰撞發生,而后面之所以會采用異或運算而不采用 &| 的原因是如果采用 & 運算會使結果偏向 1,采用 | 運算會使結果偏向 0^ 運算會使得結果能更好的保留原有特性,

put 元素流程

put 方法前面的流程上面已經提到,如果 HashMap 沒有初始化,則會進行初始化,然后再判斷當前 key 值的位置是否有元素,如果沒有元素,則直接存進去,如果有元素,則會走下面這個分支:

在這里插入圖片描述

這個 else 分支主要有 4 個邏輯:

  1. 判斷當前 key 和原有 key 是否相同,如果相同,直接回傳,
  2. 如果當前 key 和原有 key 不相等,則判斷當前桶存盤的元素是否是 TreeNode 節點,如果是則表示當前是紅黑樹,則按照紅黑樹演算法進行存盤,
  3. 如果當前 key 和原有 key 不相等,且當前桶存放的是一個鏈表,則依次遍歷每個節點的 next 節點是否為空,為空則直接將當前元素放進鏈表,不為空則先判斷兩個 key 是否相等,相等則直接回傳,不相等則繼續判斷 next 節點,直到 key 相等,或者 next 節點為空,
  4. 插入鏈表之后,如果當前鏈表長度大于 TREEIFY_THRESHOLD,默認是 8,則會將鏈表進行切換到紅黑樹存盤,

處理完之后,最后還有一個判斷就是判斷是否覆寫舊元素,如果 e != null,則說明當前 key 值已經存在,則繼續判斷 onlyIfAbsent 變數,這個變數默認就是 false,表示覆寫舊值,所以接下來會進行覆寫操作,然后再把舊值回傳,

在這里插入圖片描述

擴容

HashMap 中存盤的資料量大于閾值(負載因子 * 當前桶數量)之后,會觸發擴容操作:

在這里插入圖片描述

所以接下來讓我們看看 resize 方法:

在這里插入圖片描述

第一個紅框就是判斷當前容量是否已經達到了 MAXIMUM_CAPACITY,這個值前面提到了是 2 的 30 次冪,如果達到了這個值,就會將擴容閾值修改為 int 型別存盤的最大值,也就是不會再出發擴容了,

第二個紅框就是擴容,擴容的大小就是將舊容量左移 1 位,也就是擴大為原來的 2 倍,當然,擴大之后的容量如果不滿足第二個紅框中的條件,則會在第三個紅框中被設定,

擴容之后原有資料怎么處理

擴容很簡單,關鍵是原有的資料應該如何處理呢?不看代碼,我們可以大致梳理出遷移資料的場景,沒有資料的場景不做考慮:

  1. 當前桶位置只有自己,也就是下面沒有其他元素,
  2. 當前桶位置下面有元素,且是鏈表結構,
  3. 當前桶位置下面有元素,且是紅黑樹,

接下來讓我們看看原始碼中的 resize 方法中的資料遷移部分:

在這里插入圖片描述

紅框部分比較好理解,首先就是看當前桶內元素是否是孤家寡人,如果是,直接重新計算下標然后賦值到新資料即可,如果是紅黑樹,則打散了重組,這部分暫且略過,最后一個 else 部分就是處理鏈表部分,接下來就讓我們重點看一下鏈表的處理,

鏈表資料處理

鏈表的處理有一個核心思想:鏈表中元素的下標要么保持不變,要么在原先的基礎上在加上一個 oldCap 大小

鏈表的資料處理完整部分原始碼如下圖所示:

在這里插入圖片描述

關鍵條件就是 e.hash & oldCap,為什么這個結果等于 0 就表示元素的位置沒有發生改變呢?

在解釋這個問題之前,需要先回憶一下 tableSizeFor 方法,這個方法會將 n-1 調整為類似 00001111 的資料結構,舉個例子:比如初始化容量為 16,長度 n-1 就是 01111,而 n 就是 10000,所以如果 e.hash & oldCap ==0 就說明 hash 值的第 5 位是 010000 擴容之后得到的就是 100000,對應的 n-1 就是 011111,和原先舊的 n-1 的差異之處就是第 5 位(第 6 位是 0 不影響計算結果),所以當 e.hash & oldCap==0 就說明第 5 位對結果也沒有影響,那么就說明位置不會變,而如果 e.hash & oldCap !=0,就說明第 5 位數影響到了結果,而第 5 位如果計算結果為 1,則得到下標的位置恰好多出了一個 oldCap 的大小,即 16,其他位置擴容也是同樣的道理,所以只要 e.hash & oldCap==0,說明下標位置不變,而如果不等于 0,則下標位置應該再加上一個 oldCap

最后的回圈完節點之后,處理原始碼如下所示:

在這里插入圖片描述

同理的 32 就是 100000,這就說明了一件事,那就是只需要關注最高位的 1 即可,因為只有這一位數和 e.hash 參與 & 運算時可能得到 1

總結

本文主要分析了 HashMap 中是如何進行初始化,以及 put 方法是如何進行設定,同時也介紹了為了防止哈希沖突,將 HashMap 的容量設定為 2 的 N 次冪,最后介紹了 HashMap 中的擴容,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/260979.html

標籤:其他

上一篇:企業微信API-https請求模板-獲取access_token-Java

下一篇:如何在面試中介紹自己的專案經驗(作者原創版)

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 網閘典型架構簡述

    網閘架構一般分為兩種:三主機的三系統架構網閘和雙主機的2+1架構網閘。 三主機架構分別為內端機、外端機和仲裁機。三機無論從軟體和硬體上均各自獨立。首先從硬體上來看,三機都用各自獨立的主板、記憶體及存盤設備。從軟體上來看,三機有各自獨立的作業系統。這樣能達到完全的三機獨立。對于“2+1”系統,“2”分為 ......

    uj5u.com 2020-09-10 02:00:44 more
  • 如何從xshell上傳檔案到centos linux虛擬機里

    如何從xshell上傳檔案到centos linux虛擬機里及:虛擬機CentOs下執行 yum -y install lrzsz命令,出現錯誤:鏡像無法找到軟體包 前言 一、安裝lrzsz步驟 二、上傳檔案 三、遇到的問題及解決方案 總結 前言 提示:其實很簡單,往虛擬機上安裝一個上傳檔案的工具 ......

    uj5u.com 2020-09-10 02:00:47 more
  • 一、SQLMAP入門

    一、SQLMAP入門 1、判斷是否存在注入 sqlmap.py -u 網址/id=1 id=1不可缺少。當注入點后面的引數大于兩個時。需要加雙引號, sqlmap.py -u "網址/id=1&uid=1" 2、判斷文本中的請求是否存在注入 從文本中加載http請求,SQLMAP可以從一個文本檔案中 ......

    uj5u.com 2020-09-10 02:00:50 more
  • Metasploit 簡單使用教程

    metasploit 簡單使用教程 浩先生, 2020-08-28 16:18:25 分類專欄: kail 網路安全 linux 文章標簽: linux資訊安全 編輯 著作權 metasploit 使用教程 前言 一、Metasploit是什么? 二、準備作業 三、具體步驟 前言 Msfconsole ......

    uj5u.com 2020-09-10 02:00:53 more
  • 游戲逆向之驅動層與用戶層通訊

    驅動層代碼: #pragma once #include <ntifs.h> #define add_code CTL_CODE(FILE_DEVICE_UNKNOWN,0x800,METHOD_BUFFERED,FILE_ANY_ACCESS) /* 更多游戲逆向視頻www.yxfzedu.com ......

    uj5u.com 2020-09-10 02:00:56 more
  • 北斗電力時鐘(北斗授時服務器)讓網路資料更精準

    北斗電力時鐘(北斗授時服務器)讓網路資料更精準 北斗電力時鐘(北斗授時服務器)讓網路資料更精準 京準電子科技官微——ahjzsz 近幾年,資訊技術的得了快速發展,互聯網在逐漸普及,其在人們生活和生產中都得到了廣泛應用,并且取得了不錯的應用效果。計算機網路資訊在電力系統中的應用,一方面使電力系統的運行 ......

    uj5u.com 2020-09-10 02:01:03 more
  • 【CTF】CTFHub 技能樹 彩蛋 writeup

    ?碎碎念 CTFHub:https://www.ctfhub.com/ 筆者入門CTF時時剛開始刷的是bugku的舊平臺,后來才有了CTFHub。 感覺不論是網頁UI設計,還是題目質量,賽事跟蹤,工具軟體都做得很不錯。 而且因為獨到的金幣制度的確讓人有一種想去刷題賺金幣的感覺。 個人還是非常喜歡這個 ......

    uj5u.com 2020-09-10 02:04:05 more
  • 02windows基礎操作

    我學到了一下幾點 Windows系統目錄結構與滲透的作用 常見Windows的服務詳解 Windows埠詳解 常用的Windows注冊表詳解 hacker DOS命令詳解(net user / type /md /rd/ dir /cd /net use copy、批處理 等) 利用dos命令制作 ......

    uj5u.com 2020-09-10 02:04:18 more
  • 03.Linux基礎操作

    我學到了以下幾點 01Linux系統介紹02系統安裝,密碼啊破解03Linux常用命令04LAMP 01LINUX windows: win03 8 12 16 19 配置不繁瑣 Linux:redhat,centos(紅帽社區版),Ubuntu server,suse unix:金融機構,證券,銀 ......

    uj5u.com 2020-09-10 02:04:30 more
  • 05HTML

    01HTML介紹 02頭部標簽講解03基礎標簽講解04表單標簽講解 HTML前段語言 js1.了解代碼2.根據代碼 懂得挖掘漏洞 (POST注入/XSS漏洞上傳)3.黑帽seo 白帽seo 客戶網站被黑帽植入劫持代碼如何處理4.熟悉html表單 <html><head><title>TDK標題,描述 ......

    uj5u.com 2020-09-10 02:04:36 more
最新发布
  • 2023年最新微信小程式抓包教程

    01 開門見山 隔一個月發一篇文章,不過分。 首先回顧一下《微信系結手機號資料庫被脫庫事件》,我也是第一時間得知了這個訊息,然后跟蹤了整件事情的經過。下面是這起事件的相關截圖以及近日流出的一萬條資料樣本: 個人認為這件事也沒什么,還不如關注一下之前45億快遞資料查詢渠道疑似在近日復活的訊息。 訊息是 ......

    uj5u.com 2023-04-20 08:48:24 more
  • web3 產品介紹:metamask 錢包 使用最多的瀏覽器插件錢包

    Metamask錢包是一種基于區塊鏈技術的數字貨幣錢包,它允許用戶在安全、便捷的環境下管理自己的加密資產。Metamask錢包是以太坊生態系統中最流行的錢包之一,它具有易于使用、安全性高和功能強大等優點。 本文將詳細介紹Metamask錢包的功能和使用方法。 一、 Metamask錢包的功能 數字資 ......

    uj5u.com 2023-04-20 08:47:46 more
  • vulnhub_Earth

    前言 靶機地址->>>vulnhub_Earth 攻擊機ip:192.168.20.121 靶機ip:192.168.20.122 參考文章 https://www.cnblogs.com/Jing-X/archive/2022/04/03/16097695.html https://www.cnb ......

    uj5u.com 2023-04-20 07:46:20 more
  • 從4k到42k,軟體測驗工程師的漲薪史,給我看哭了

    清明節一過,盲猜大家已經無心上班,在數著日子準備過五一,但一想到銀行卡里的余額……瞬間心情就不美麗了。最近,2023年高校畢業生就業調查顯示,本科畢業月平均起薪為5825元。調查一出,便有很多同學表示自己又被平均了。看著這一資料,不免讓人想到前不久中國青年報的一項調查:近六成大學生認為畢業10年內會 ......

    uj5u.com 2023-04-20 07:44:00 more
  • 最新版本 Stable Diffusion 開源 AI 繪畫工具之中文自動提詞篇

    🎈 標簽生成器 由于輸入正向提示詞 prompt 和反向提示詞 negative prompt 都是使用英文,所以對學習母語的我們非常不友好 使用網址:https://tinygeeker.github.io/p/ai-prompt-generator 這個網址是為了讓大家在使用 AI 繪畫的時候 ......

    uj5u.com 2023-04-20 07:43:36 more
  • 漫談前端自動化測驗演進之路及測驗工具分析

    隨著前端技術的不斷發展和應用程式的日益復雜,前端自動化測驗也在不斷演進。隨著 Web 應用程式變得越來越復雜,自動化測驗的需求也越來越高。如今,自動化測驗已經成為 Web 應用程式開發程序中不可或缺的一部分,它們可以幫助開發人員更快地發現和修復錯誤,提高應用程式的性能和可靠性。 ......

    uj5u.com 2023-04-20 07:43:16 more
  • CANN開發實踐:4個DVPP記憶體問題的典型案例解讀

    摘要:由于DVPP媒體資料處理功能對存放輸入、輸出資料的記憶體有更高的要求(例如,記憶體首地址128位元組對齊),因此需呼叫專用的記憶體申請介面,那么本期就分享幾個關于DVPP記憶體問題的典型案例,并給出原因分析及解決方法。 本文分享自華為云社區《FAQ_DVPP記憶體問題案例》,作者:昇騰CANN。 DVPP ......

    uj5u.com 2023-04-20 07:43:03 more
  • msf學習

    msf學習 以kali自帶的msf為例 一、msf核心模塊與功能 msf模塊都放在/usr/share/metasploit-framework/modules目錄下 1、auxiliary 輔助模塊,輔助滲透(埠掃描、登錄密碼爆破、漏洞驗證等) 2、encoders 編碼器模塊,主要包含各種編碼 ......

    uj5u.com 2023-04-20 07:42:59 more
  • Halcon軟體安裝與界面簡介

    1. 下載Halcon17版本到到本地 2. 雙擊安裝包后 3. 步驟如下 1.2 Halcon軟體安裝 界面分為四大塊 1. Halcon的五個助手 1) 影像采集助手:與相機連接,設定相機引數,采集影像 2) 標定助手:九點標定或是其它的標定,生成標定檔案及內參外參,可以將像素單位轉換為長度單位 ......

    uj5u.com 2023-04-20 07:42:17 more
  • 在MacOS下使用Unity3D開發游戲

    第一次發博客,先發一下我的游戲開發環境吧。 去年2月份買了一臺MacBookPro2021 M1pro(以下簡稱mbp),這一年來一直在用mbp開發游戲。我大致分享一下我的開發工具以及使用體驗。 1、Unity 官網鏈接: https://unity.cn/releases 我一般使用的Apple ......

    uj5u.com 2023-04-20 07:40:19 more