Java中HashSet、HashMap的低層數(shù)據(jù)結構

以下先講解HashMap

開始先簡單總結:

  • HashMap在JDK8之底層:數(shù)組 + 鏈表
  • HashMap在JDK8之底層:數(shù)組 + 鏈表 + 紅黑樹

在底層存儲的時候必定有數(shù)組,鏈表與紅黑樹是在特定情況下產(chǎn)生的。

01BCB5C6-6444-4D64-A201-C1FB0BD35822.png

1.HashMap底層

1.1Hash底層存儲結構

1.首先HashMap中的Key與Value是存儲在Node對象中,作為Node對象的屬性出現(xiàn):


image.png

2.Node對象是存儲在Node數(shù)組中:


image.png

3.最重要的一點,Node存儲在數(shù)組中的模式使用的是Set集合的存儲思想:無序、不可重復

無序:在存儲到Node[]數(shù)組的時候,并不是順序存儲,而是根據(jù)key的hash值計算得來的存儲位置。
不可重復:Key是不可重復的,為什么呢?后面會講。

現(xiàn)在我們知道了:HashMap集合Key與Value存儲在對象中,對象存儲在數(shù)組中
Key、Value --> 對象 --> 數(shù)組
如下圖:

image.png

1.2 HashMap為什么是無序的?不可重復的?

1.向HashMap中添加一組值:map.put(key, value);

  1. 計算hash值:這時系統(tǒng)會根據(jù)key值,調(diào)用key所在類的hashCode()方法計算hash值

所以為什么存儲在map中的對象,需要所在類重寫hashCode()方法

  1. 計算index值:再根據(jù)hash值計算index index = (length - 1) & hash (length當前底層數(shù)組的長度) index就是存儲在數(shù)組中的位置(下標)。

總結:因為Node對象存儲在數(shù)組中的位置是根據(jù)Key值計算得來,所以并不是順序存儲進去的(所以是無序的),同時因為相同的Key計算出來的index也是相同的,當Key相同時會用當前node替換原始的node,但是還是保持一個map中key值是唯一的(不可重復性)體現(xiàn)在下面源碼中:

其中 p 是新增的 node,e是原始的 node:如果hash值相同,key相同,就用新增的替換原來的


image.png

我們看到還調(diào)用了 key.equals(k), 前面的 k == key 判斷地址,后面 equals 判斷內(nèi)容,保持絕對相等

所以我們除了為什么無序、不可重復,還得出結論:存儲在HashMap中的對象需要重寫 hashCode()equals() 這兩個方法

為什么要使用hash值計算index進行無序存儲呢?當我們查找一個key對應的value的時候,就不需要順序遍歷底層數(shù)組了,直接計算index值,去對應位置拿就行。比list查找效率大大提升。而且map集合設計之初就是用來根據(jù)key取value值的存儲結構,需要承擔大量查找操作。

1.3 鏈表與紅黑樹是如何產(chǎn)生的

1.當我們添加一組值的時候,map.put(key, value);通過key的hash值計算得到的index,拿著index去數(shù)組中存儲的時候發(fā)現(xiàn)該位置已經(jīng)有數(shù)據(jù)了怎么辦?

image.png

2.那么就會先比較 hash 值,再比較 key 地址值,最后比較key的內(nèi)容是否相等,如果還是相等呢?那么node中的next屬性就派上用場了:


image.png

3.系統(tǒng)就會將當前對象node賦值給next,讓它鏈接到原始node的后面(jdk8之后是新node鏈接到原始的后面,jdk8之前是把新的對象放在數(shù)組中,把原始對象鏈接到新node的后面,總之都是形成一個單向鏈表)就形成了:數(shù)組 + 鏈表

image.png

4.什么時候會形成紅黑樹呢?提到下面兩個關鍵的變量:


image.png

5.當鏈表長度達到8,且數(shù)組長度達到64時候就會轉換為紅黑樹進行存儲。如果鏈表長度達到8,數(shù)組長度還未小于64,就會先進性擴容。

HashMap的每一次擴容都會重新計算每個node的index,因為 index = (length - 1) & hash,index與數(shù)組長度有關。這樣也能減少鏈表與紅黑樹的出現(xiàn)。

6.所以后續(xù)就有可能形成:數(shù)組+鏈表+紅黑樹 的情況(也不排除偶然:每個計算的index都不相同,只有數(shù)組,那就太爽了):

8048D9C0-711B-463A-BA24-AA6DD7B39489.png

1.4 HashMap何時會進行擴容?

List 默認底層默認是長度為10的數(shù)組,存滿了(進來第11個元素),就擴容。
HashMap 底層默認是長度為16的數(shù)組,加載因子是0.75,也就是說存儲超過數(shù)組長度在75%也就第一次擴容是12個的時候就會進行擴容。


image.png

為什么不存儲滿了再擴容呢?因為存儲位置是計算出來的,萬一計算出來的index就是不會出現(xiàn)2,那么2這個位置永遠不會存儲元素。那就完蛋。
同時,如上面描述的也會在長度未達到64,鏈表長度就達到8的時候擴容。

現(xiàn)在我們就基本講完了HashMap的底層了。

2.LinkedHashMap

其實也就是在HashMap的基礎上增加了雙向指針,指向前一個,后一個,方便遍歷。


image.png

3.HashSet

為什么放在最后講,因為Set集合的底層就是調(diào)用了HashMap:
創(chuàng)建的時候構造器new了一個HashMap:


image.png

添加的時候調(diào)用HashMap的put方法:


image.png

只不過在一開始new了一個單例的Object作為value:


image.png

4.LinkedHashSet

實際上LinkedHashSet底層就是一個LinkedHashMap

LinkedHashSet構造器調(diào)用了HashSet中的構造器


image.png

HashSet中的這個構造器創(chuàng)建LinkedHashMap對象返回。


image.png

總結:
HashMap
默認初始化數(shù)組長度:16
擴容倍數(shù):2倍
HashMap的默認加載因子:0.75
Bucket中鏈表長度大于該默認值,轉化為紅黑樹 : 8
桶中的Node被樹化時最小的hash表容量:64

image.png

HashMap 是大哥
LinkedHashMap 就是在 HashMap 基礎上增加雙向鏈表
HashSet 底層就是 HashMap
LinkedHashSet 底層就是 LinkedHashMap

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容