久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

Java7/8中的HashMap和ConcurrentHashMap源碼分析,,看了都說(shuō)好

 西北望msm66g9f 2018-11-29

專(zhuān)注于編程,、互聯(lián)網(wǎng)動(dòng)態(tài)。最終將總結(jié)的技術(shù),、心得,、經(jīng)驗(yàn)(數(shù)據(jù)結(jié)構(gòu)與算法、源碼分析等)享給大家,,這里不只限于技術(shù),!還有職場(chǎng)心得、生活感悟,、以及面經(jīng),。點(diǎn)擊上方 '程序員小樂(lè)' ,關(guān)注公眾號(hào),,第一時(shí)間送達(dá),!

每日英文 

Do not deliberately pursue certain things, falling leaves, belongs to me, it'll always be back. 

不去刻意追求某些東西,落葉歸根,,屬于我的,,它總會(huì)回來(lái)。


樂(lè)樂(lè)有話說(shuō) 

成熟不是看你的年齡有多大,而是看你的肩膀能挑起多重的責(zé)任,。


來(lái)自:habit_learning

鏈接:https://www.jianshu.com/p/39a57484932e

封面來(lái)自網(wǎng)絡(luò)

00 導(dǎo)讀  

本文分析的是源碼,,所以至少讀者要熟悉他們的接口使用,同時(shí),,對(duì)于并發(fā),,讀者至少要知道 CAS、ReentrantLock,、UNSAFE操作這幾個(gè)基本知識(shí),,文中不會(huì)對(duì)這些知識(shí)進(jìn)行介紹。Java8 用到了紅黑樹(shù),,不過(guò)本文不會(huì)進(jìn)行展開(kāi),,感興趣的讀者請(qǐng)自行查找相關(guān)資料。

01 Hash 表  

在講HashMap之前,,我們先來(lái)了解下他們底層實(shí)現(xiàn)的一種數(shù)據(jù)結(jié)構(gòu)——Hash 表,。

Hash表,是根據(jù)關(guān)鍵碼值(Key value)而直接進(jìn)行訪問(wèn)的數(shù)據(jù)結(jié)構(gòu),。也就是說(shuō),,它通過(guò)把關(guān)鍵碼值映射到表中一個(gè)位置來(lái)訪問(wèn)記錄,以加快查找的速度,。存放記錄的數(shù)組叫做哈希表,。

在HashMap中,就是將所給的“鍵”通過(guò)哈希函數(shù)得到“索引”,,然后把內(nèi)容存在數(shù)組中,,這樣就形成了“鍵”和內(nèi)容的映射關(guān)系。

哈希表

上圖可以發(fā)現(xiàn),,“鍵”轉(zhuǎn)換為“索引”的過(guò)程就是哈希函數(shù),,為了盡可能保證每一個(gè)“鍵”通過(guò)哈希函數(shù)的轉(zhuǎn)換對(duì)應(yīng)不同的“索引”,就需要對(duì)哈希函數(shù)進(jìn)行選擇了,,使其得到的“索引”分布越均勻越好,。

哈希函數(shù)的均勻性:

哈希函數(shù)設(shè)計(jì)

通過(guò)前輩的研究加上實(shí)踐表明,當(dāng)把哈希函數(shù)得到hashcode值對(duì)素?cái)?shù)取模時(shí),,這樣得到的索引是最為均勻的,。但是,在HashMap源碼中,,并不是取模素?cái)?shù)的,,而是一種等效取模2的n次方的位運(yùn)算,hash&(length-1),。hash%length==hash&(length-1)的前提是length是2的n次方,;之所以使用位運(yùn)算替代取模,,是因?yàn)槲贿\(yùn)算的效率更高,所以也就要求數(shù)組的長(zhǎng)度必須是2的n次方(索引的分布也是很均勻的),。

哈希函數(shù)的一致性:

哈希函數(shù)設(shè)計(jì)原則

哈希函數(shù)的一致性原則是:當(dāng)兩個(gè)對(duì)象的equals相等,,那么他們的hashcode一定相等。

這就要求我們?cè)谥貙?xiě)了equals方法時(shí),,必須重寫(xiě)hashcode方法,。如果不重寫(xiě)hashcode,則會(huì)使用Object的hashcode方法,,該方法是以我們創(chuàng)建的對(duì)象的地址作為參數(shù)求hash的,。所以,如果不重寫(xiě)hashcode,,兩個(gè)equals相等的對(duì)象會(huì)導(dǎo)致hashcode不同(因?yàn)椴煌膶?duì)象),,這個(gè)是不允許的,因?yàn)檫`背了hash函數(shù)的一致性原則,。

哈希沖突:

當(dāng)兩個(gè)不同的元素,,通過(guò)哈希函數(shù)得到了同一個(gè)hashcode,則會(huì)產(chǎn)生哈希沖突,。HashMap的處理方式是,,JDK8之前,每一個(gè)位置對(duì)應(yīng)一個(gè)鏈表,,鏈?zhǔn)降拇娣殴_突的元素;JDK8開(kāi)始,,當(dāng)哈希沖突達(dá)到一定程度(8個(gè)),,每一個(gè)位置從鏈表轉(zhuǎn)換成紅黑樹(shù)。因?yàn)榧t黑樹(shù)的時(shí)間復(fù)雜度是O(log n)的,,效率優(yōu)于鏈表,。

鏈地址法

哈希表小結(jié):

哈希表,均攤復(fù)雜度是O(1),,因?yàn)榈谝徊酵ㄟ^(guò)數(shù)組索引找到數(shù)組位置是O(1),,然后到鏈表中查找元素的均攤復(fù)雜度是O(size/length),size為元素個(gè)數(shù),,length為數(shù)組長(zhǎng)度,。由于Hash表的容量是動(dòng)態(tài)擴(kuò)容的,也就是說(shuō)隨著size和length成正比的,,即size/length是一個(gè)常數(shù),,于是也是O(1)的復(fù)雜度,即總的來(lái)說(shuō),,均攤復(fù)雜度是O(1),。但是哈希表是沒(méi)有順序性的,即無(wú)法對(duì)元素進(jìn)行排序。

哈希表的缺點(diǎn)

02 Java7 HashMap  

HashMap 是最簡(jiǎn)單的,,一來(lái)我們非常熟悉,,二來(lái)就是它不支持并發(fā)操作,所以源碼也非常簡(jiǎn)單,。

首先,,我們用下面的這張圖來(lái)介紹下HashMap的結(jié)構(gòu)。

Java7 HashMap結(jié)構(gòu)圖

大方向上,,HashMap里面是一個(gè)數(shù)組,,然后每個(gè)數(shù)組中的每個(gè)元素是一個(gè)單向鏈表。

上圖中,,每個(gè)綠色的實(shí)體是嵌套類(lèi)Entry的實(shí)例,,Entry包含四個(gè)屬性:key,value,hash值和用于單向鏈表的next。

capacity:當(dāng)前數(shù)組容量,,始終保持2^n,,可以擴(kuò)容,擴(kuò)容后的大小為當(dāng)前的2倍,,默認(rèn)為16,。

loadFactor:負(fù)載因子,默認(rèn)為 0.75,。

threshold:擴(kuò)容的閾值,,等于 capatity * loadFactor。

put 過(guò)程分析

put 源碼

1,、數(shù)組初始化:在第一個(gè)元素插入 HashMap 的時(shí)候做一次數(shù)組的初始化,,就是先確定初始的數(shù)組大小,并計(jì)算數(shù)組的擴(kuò)容閾值,。這里將數(shù)組保持在 2 的 n 次方的做法,,java7 和 java8 的HashMap 和 ConcurrentHashMap 都有相應(yīng)的要求。

注意:new HashMap時(shí),,并未做數(shù)組初始化操作,,而是簡(jiǎn)單為loadFactor ,threshold賦值,;同時(shí)為了保證將數(shù)組保持在 2 的 n 次方,,會(huì)對(duì) capacity的值進(jìn)行處理,取大于capacity且最近的2 的 n 次方值作為數(shù)組大小,。

2,、計(jì)算具體數(shù)組的下標(biāo):使用 key的 hash值對(duì)數(shù)組長(zhǎng)度進(jìn)行取模(源碼中是 hash & (length -1),當(dāng)length是2^n 時(shí),,此時(shí)(length - 1) 的二進(jìn)制全是1,, hash & (length -1) 相當(dāng)于取 hash值的低 n位,, 結(jié)果和 hash mod length一樣的)。

3,、找到數(shù)組下標(biāo)后,,先進(jìn)行 key 判重(== || equals),如果沒(méi)有重復(fù),,則將新值放入鏈表的表頭,,如果有重復(fù),直接用新值覆蓋舊值,。

4,、數(shù)組擴(kuò)容:如果當(dāng)前size >= threshold,那么就會(huì)觸發(fā)擴(kuò)容,;擴(kuò)容就是用一個(gè)新的大數(shù)組替換原來(lái)的小數(shù)組,,并將原來(lái)數(shù)組中的值遷移到新的數(shù)組中。

既然對(duì) key的判重依據(jù)是hash值和equals方法,,故必須對(duì)key進(jìn)行重寫(xiě)hashcode和equals方法,,因?yàn)槿绻恢貙?xiě),將直接用Object的hashcode和equals方法,,而其必須是同一個(gè)對(duì)象才能滿足key相同,。

hashcode方法的一般規(guī)定:如果兩個(gè)對(duì)象能夠equals相等,那么他們的hashcode必須相等,。

所以,,重寫(xiě)了equals必須重寫(xiě)hashcode。

get 過(guò)程分析

java7 HashMap get源碼

相對(duì)于put 過(guò)程,,get 過(guò)程是非常簡(jiǎn)單的,。

1、根據(jù)key 計(jì)算 hash 值,。

2、找到相應(yīng)的數(shù)組下標(biāo):hash & (length - 1),。

3,、遍歷該數(shù)組位置處的鏈表,直到找到相等(== || equals)的 key,。

03 java7 ConcurrentHashMap  

ConcurrentHashMap和 HashMap 思路是差不多的,,但是因?yàn)樗С植l(fā)操作,所以要復(fù)雜一些,。

整個(gè)ConcurrentHashMap 由一個(gè)個(gè) Segment 組成,,Segment 代表“部分” 或 “一段”的意思,所以很多地方都會(huì)將其描述為分段鎖,。

簡(jiǎn)單的理解,,ConcurrentHashMap 是一個(gè) Segment數(shù)組,,Segment 通過(guò)繼承 ReentrantLock來(lái)進(jìn)行加鎖,所以每次需要加鎖的操作鎖住的是一個(gè) Segment,,這樣只要保證每個(gè) Segment是線程安全的,,也就實(shí)現(xiàn)了全局的線程安全性。

Java7 ConcurrentHashMap 結(jié)構(gòu)

concurrencyLevel:并行級(jí)別,、并發(fā)數(shù),、Segment 數(shù)。默認(rèn)是16,,也就是說(shuō) ConcurrentHashMap 有16個(gè) Segment,,所以理論上,最多同時(shí)支持16個(gè)線程并發(fā)寫(xiě),。這個(gè)值可以在初始化的時(shí)候設(shè)置為其他值,,但是一旦初始化以后,它就不可以擴(kuò)容了,。

再具體到每個(gè) Segment內(nèi)部,,其實(shí)每個(gè) Segment 很像之前介紹的 HashMap,不過(guò)它要保證線程安全,,所以處理起來(lái)要麻煩些,。

初始化

initialCapacity:初始容量,這個(gè)值指的是整個(gè) ConcurrentHashMap的初始容量,,實(shí)際操作的時(shí)候需要平均分給每個(gè) Segment,。

loadFactor:負(fù)載因子,之前我們說(shuō)了,,Segment 數(shù)組不可以擴(kuò)容,,所以這個(gè)負(fù)載因子是給每個(gè) Segment 內(nèi)部使用的。

new ConcurrentHashMap()無(wú)參進(jìn)行初始化以后:

1,、Segment 數(shù)組長(zhǎng)度為16,,不可以擴(kuò)容。

2,、Sement[i]的默認(rèn)大小為2,,負(fù)載因子為0.75,得出初始閾值為1.5,,也就插入第一個(gè)元素不會(huì)觸發(fā)擴(kuò)容,,插入第二個(gè)進(jìn)行一次擴(kuò)容。

3,、初始化了 Segment[0],,其他位置還是null。

put 過(guò)程分析

第一層皮很簡(jiǎn)單,,根據(jù) hash值找到 Segment,,之后就是 Segment 內(nèi)部的 put 操作了,。

Segment 內(nèi)部是由 數(shù)組+鏈表 組成的。

put 源碼

初始化 Segment:ensureSement

ConcurrentHashMap 初始化的時(shí)候初始化了第一個(gè)分段 Segment[0],,對(duì)于其他分段來(lái)說(shuō),,在插入第一個(gè)值的時(shí)候進(jìn)行初始化。并且初始化其他的 Segment[k]時(shí),,需要當(dāng)前 Segment[0]處的數(shù)組長(zhǎng)度和負(fù)載因子,,故 Segment[0]需要在ConcurrentHashMap初始化的時(shí)候進(jìn)行初始化。

擴(kuò)容:rehash

重復(fù)一下,,Segment 數(shù)組不能擴(kuò)容,,擴(kuò)容的是 單個(gè)Segment內(nèi)部數(shù)組 HashEntry[],擴(kuò)容后,,容量為原來(lái)的2倍,。

觸發(fā)擴(kuò)容的時(shí)機(jī):put 的時(shí)候,如果判斷該值的插入會(huì)導(dǎo)致該 Segment內(nèi)部的元素個(gè)數(shù)超過(guò)閾值,,那么先進(jìn)行擴(kuò)容,,再插值。該方法不需要考慮并發(fā),,因?yàn)?put 操作,,是持有獨(dú)占鎖的。

get 過(guò)程分析

相對(duì)于 put 來(lái)說(shuō),,get 真的不要太簡(jiǎn)單,。

get 源碼

1、計(jì)算 hash值,,找到 Segment數(shù)組中的下標(biāo),。

2、再次根據(jù) hash 找到每個(gè) Segment內(nèi)部的 HashEntry[]數(shù)組的下標(biāo),。

3,、遍歷該數(shù)組位置處的鏈表,直到找到相等(== || equals)的 key,。

get 是不需要加鎖的:原因是get方法是將共享變量(table)定義為volatile,,讓被修飾的變量對(duì)多個(gè)線程可見(jiàn)(即其中一個(gè)線程對(duì)變量進(jìn)行了修改,會(huì)同步到主內(nèi)存,,其他線程也能獲取到最新值),,允許一寫(xiě)多讀的操作,。

04 Java8 HashMap  

Java8 對(duì) HashMap 進(jìn)行了一些修改,,最大的是不同就是利用了紅黑樹(shù),所以其由 數(shù)組+鏈表+紅黑樹(shù) 組成,。

根據(jù) Java7 HashMap 的介紹,,我們知道,,查找的時(shí)候,根據(jù) hash 值我們能夠快速定位到數(shù)組的具體下標(biāo),,但是之后的話,,需要順著鏈表一個(gè)個(gè)比較下去才能找到我們需要的,時(shí)間復(fù)雜度取決于鏈表的長(zhǎng)度,,為O(N),。

為了降低這部分的開(kāi)銷(xiāo),在Java8 中,,當(dāng)鏈表中的元素超過(guò)8個(gè)以后,,會(huì)將鏈表轉(zhuǎn)換為紅黑樹(shù),在這些位置進(jìn)行查找的時(shí)候可以降低時(shí)間復(fù)雜度為O(logN),。

Java8 HashMap 結(jié)構(gòu)

Java7 中使用 Entry來(lái)代表每個(gè) HashMap 中的數(shù)據(jù)節(jié)點(diǎn),,Java8 中使用 Node,基本沒(méi)有區(qū)別,,都是 key,,value,hash和 next 這四個(gè)屬性,,不過(guò),, Node 只能用于鏈表的情況,紅黑樹(shù)需要使用 TreeNode,。

put 過(guò)程分析

put 源碼

和Java7 稍微有點(diǎn)不一樣的是,,Java7 是先擴(kuò)容后插入新值的,Java8 先插入值再擴(kuò)容的,;Java7 (HashMap/ConcurrentHashMap) 是將數(shù)據(jù)插入到鏈表的表頭,,而 Java8 是將數(shù)據(jù)插入到鏈表的最后面。

get 過(guò)程分析

get 源碼

1,、計(jì)算 key 的 hash 值,,根據(jù) hash 值找到對(duì)應(yīng)數(shù)組下標(biāo):hash & (length - 1)。

2,、判斷數(shù)組該位置第一個(gè)節(jié)點(diǎn)是否剛好是我們要找的(判key),,如果不是,走第三步,。

3,、判斷該元素類(lèi)型是否是 TreeNode,如果是,,用紅黑樹(shù)的方法取數(shù)據(jù),,如果不是,走第四步,。

4,、遍歷鏈表,,直到找到相等(== || equals)的 key。

05 Java8 ConcurrentHashMap  

對(duì)于 ConcurrentHashMap,,Java8 也引入了紅黑樹(shù),。

Java8 ConcurrentHashMap 結(jié)構(gòu)

結(jié)構(gòu)上和 Java8 的 HashMap 基本上一樣,不過(guò)它要保證安全性,,所以源碼上確實(shí)要復(fù)雜一些,。

初始化

初始化

這個(gè)初始化方法,通過(guò)提供初始容量,,計(jì)算 sizeCtl,,sizeCtl = [(1.5 * initialCapacity + 1,然后向上取最近的2的 n 次方)],。

put 過(guò)程分析

1,、獲取 key 的 hash值,找到數(shù)組下標(biāo),。

2,、如果數(shù)組為空,進(jìn)行數(shù)組初始化,。

3,、判斷該數(shù)組第一個(gè)節(jié)點(diǎn)是否有數(shù)據(jù),如果沒(méi)有數(shù)據(jù),,則使用CAS操作將這個(gè)新值插入,,如果有數(shù)據(jù),則進(jìn)入第四步,。

4,、對(duì)頭結(jié)點(diǎn)進(jìn)行加鎖(synchronized),如果頭結(jié)點(diǎn)的 hash 值>=0,,說(shuō)明是鏈表,,遍歷鏈表,如果找到相等的key,,則進(jìn)行覆蓋,,如果沒(méi)有找到相等的key,則將新值放到鏈表的最后面,;如果 hash 值 <>

5,、插值完成之后,判斷鏈表元素是否達(dá)到臨界值(8),,那么就會(huì)進(jìn)行紅黑樹(shù)轉(zhuǎn)換,。注意:當(dāng)數(shù)組長(zhǎng)度小于64時(shí),即使達(dá)到臨界值也不會(huì)進(jìn)行紅黑樹(shù)轉(zhuǎn)換,而是進(jìn)行數(shù)組擴(kuò)容(Java8 HashMap沒(méi)有此限制,,直接判斷臨界值即可)。

初始化數(shù)組:initTable

初始化方法中的并發(fā)問(wèn)題是通過(guò)對(duì) sizeCtl 進(jìn)行一個(gè)CAS 操作來(lái)控制的,。

initTable

get 過(guò)程分析

get 源碼

1,、計(jì)算 hash值

2、根據(jù) hash 值找到數(shù)組對(duì)應(yīng)的位置:(n - 1) & hash

3.根據(jù)該位置處頭節(jié)點(diǎn)的性質(zhì)進(jìn)行查找:

1)如果該位置為 null,,那么直接返回 null 就可以了
2)如果該位置處的節(jié)點(diǎn)剛好就是我們需要的,,返回該節(jié)點(diǎn)的值即可
3)如果該位置節(jié)點(diǎn)的 hash 值小于 0,說(shuō)明正在擴(kuò)容,,或者是紅黑樹(shù),,后面調(diào)用 find 接口,程序會(huì)根據(jù)上下文執(zhí)行具體的方法,。
4)如果以上 3 條都不滿足,,那就是鏈表,進(jìn)行遍歷比對(duì)即可

06 總結(jié)  

HashMap 是允許 key為null值的,,并且將其保存在數(shù)組第一個(gè)元素 table[0]上,;而 ConcurrentHashMap 是不允許 key 為 null的。

LinkedHashMap 是默認(rèn)根據(jù)元素增加的先后順序進(jìn)行排序,,而 TreeMap(底層二叉樹(shù)) 則默認(rèn)根據(jù)元素的 Key 進(jìn)行自然排序(即從小到大),,也可以指定排序的比較器。

HashTable:線程安全的,,但是由于采用的是synchronized來(lái)保證線程安全,,效率非常低,因?yàn)楫?dāng)一個(gè)線程訪問(wèn)HashTable的同步方法時(shí),,其他線程訪問(wèn)會(huì)進(jìn)入阻塞或輪詢的狀態(tài),。

二叉樹(shù):二叉樹(shù)規(guī)定了在每個(gè)節(jié)點(diǎn)下最多只能擁有兩個(gè)子節(jié)點(diǎn),一左一右,。

二叉查找樹(shù):二叉查找樹(shù)的左子樹(shù)中任意節(jié)點(diǎn)的值都小于右子樹(shù)中任意節(jié)點(diǎn)的值,。

平衡二叉樹(shù):又稱為AVL樹(shù),它要求左右子樹(shù)的高度差的絕對(duì)值不能大于1,,紅黑樹(shù)就是一種平衡二叉樹(shù),。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多