一文讀懂JDK7,8,9的hashmap,hashtable,concurrenthashmap及他們的區別

weixin_34253539發表於2018-10-31

內容和標題一樣長哦,人家寫了好久的。如無特別指明,內容對應的原始碼是jdk1.7(後面會和1.8對比)

1:hashmap簡介(如下,陣列-連結串列形式)

HashMap的儲存結構

圖中,紫色部分即代表雜湊表,也稱為雜湊陣列(預設陣列大小是16,每對key-value鍵值對其實是存在map的內部類entry裡的),陣列的每個元素都是一個單連結串列的頭節點,跟著的綠色連結串列是用來解決衝突的,如果不同的key對映到了陣列的同一位置處,就會採用頭插法將其放入單連結串列中。

2:hashmap原理(即put和get原理)

2.1 put原理

1.根據key獲取對應hash值:int hash = hash(key.hash.hashcode())

2.根據hash值和陣列長度確定對應陣列引int i = indexFor(hash, table.length); 簡單理解就是i = hash值%模以 陣列長度(其實是按位與運算)。如果不同的key都對映到了陣列的同一位置處,就將其放入單連結串列中。且新來的是放在頭節點。

2.2 get原理

1.通過hash獲得對應陣列位置,遍歷該陣列所在連結串列(key.equals())

3.1:hashcode相同,衝突怎麼辦?

“頭插法”,放到對應的連結串列的頭部。

3.2:為什麼是頭插法(為什麼這麼設計)?

因為HashMap的發明者認為,後插入的Entry被查詢的可能性更大,所以放在頭部(因為get()查詢的時候會遍歷整個連結串列)。

4.1:hashmap的預設陣列長度是多少?

預設為16

4.2:為什麼?

之所以選擇16,是為了服務於從key對映到index的hash演算法(看下面)。

5.1:hashmap達到預設負載因子(0.75)怎麼辦?

自動雙倍擴容,擴容後重新計算每個鍵值對位置。且長度必須為16或者2的冪次

5.2:為啥要16或者2的冪次?

若不是16或者2的冪次,位運算的結果不夠均勻分佈,顯然不符合Hash演算法均勻分佈的原則。

反觀長度16或者其他2的冪,Length-1的值是所有二進位制位全為1,這種情況下,index的結果等同於HashCode後幾位的值。只要輸入的HashCode本身分佈均勻,Hash演算法的結果就是均勻的。

6.1:hashmap是執行緒安全的嗎?

不是。

6.2 :為什麼?

因為沒加鎖

6.3: 那在併發時會導致什麼問題?

hashmap在接近臨界點時,若此時兩個或者多個執行緒進行put操作,都會進行resize(擴容)和ReHash(為key重新計算所在位置),而ReHash在併發的情況下可能會形成連結串列環。在執行get的時候,會觸發死迴圈,引起CPU的100%問題。

注:jdk8已經修復hashmap這個問題了,jdk8中擴容時保持了原來連結串列中的順序。但是HashMap仍是非併發安全,在併發下,還是要使用ConcurrentHashMap。

6.4: 如何判斷有環形表?

最優:首先建立兩個指標A和B(在java裡就是兩個物件引用),同時指向這個連結串列的頭節點。然後開始一個大迴圈,在迴圈體中,讓指標A每次向下移動一個節點,讓指標B每次向下移動兩個節點,然後比較兩個指標指向的節點是否相同。如果相同,則判斷出連結串列有環,如果不同,則繼續下一次迴圈。

理解例子:在一個環形跑道上,兩個運動員在同一地點起跑,一個運動員速度快,一個運動員速度慢。當兩人跑了一段時間,速度快的運動員必然會從速度慢的運動員身後再次追上並超過,原因很簡單,因為跑道是環形的。

7: hashmap 和 hashtable 區別?

兩者的區別執行緒效率陣列預設值null值hashmap不安全更高16key-value都允許hashtable安全略低11不允許(拋異常)

8.0:那hashmap不安全,hashtable效能又低,怎麼辦?

用concurrenthashmap,即保證安全,效能又可以保證。

8.1:那concurrenthashmap究竟是什麼?

整個ConcurrentHashMap的結構如下:

理解:hashmap是有entry陣列組成,而concurrenthashmap則是Segment陣列組成。而Segment又是什麼呢?Segment本身就相當於一個HashMap。

同HashMap一樣,Segment包含一個HashEntry陣列,陣列中的每一個HashEntry既是一個鍵值對,也是一個連結串列的頭節點。

單一的Segment結構如下(是不是看著就是hashmap):

像這樣的Segment物件,在ConcurrentHashMap集合中有多少個呢?有2的N次方個,共同儲存在一個名為segments的陣列當中。

可以說,ConcurrentHashMap是一個二級雜湊表。在一個總的雜湊表下面,有若干個子雜湊表。(這樣類比理解多個hashmap組成一個cmap)

8.2:那他的put和get方法呢?

Put方法:

1.為輸入的Key做Hash運算,得到hash值。

2.通過hash值,定位到對應的Segment物件

3.獲取可重入鎖

4.再次通過hash值,定位到Segment當中陣列的具體位置。

5.插入或覆蓋HashEntry物件。

6.釋放鎖。

Get方法:

1.為輸入的Key做Hash運算,得到hash值。

2.通過hash值,定位到對應的Segment物件

3.再次通過hash值,定位到Segment當中陣列的具體位置。

由此可見,和hashmap相比,ConcurrentHashMap在讀寫的時候都需要進行二次定位。先定位到Segment,再定位到Segment內的具體陣列下標。

9: hashmap 和 concurrenthashmap區別?

執行緒: 不安全 安全

10.1:為啥concurrenthashmap和hashtable都是執行緒安全,但是前者效能更高

因為前者是用的分段鎖,根據hash值鎖住對應Segment物件,當hash值不同時,使其能實現並行插入,效率更高,而hashtable則會鎖住整個map。

如何理解並行插入:當cmap需要put元素的時候,並不是對整個map進行加鎖,而是先通過hashcode來知道他要放在那一個分段(Segment物件)中,然後對這個分段進行加鎖,所以當多執行緒put的時候,只要不是放在同一個分段中,就實現了真正的並行的插入。

但是,在統計size的時候,就是獲取concurrenthashmap全域性資訊的時候,就需要獲取所有的分段鎖才能統計(即效率稍低)。

10.2:分段鎖的設計解決的是什麼問題?

分段鎖的設計目的是細化鎖的粒度,當操作不需要更新整個陣列的時候,就僅僅針對陣列中的一部分行加鎖操作。

11:JDK1.7的hashmap和JDK1.8的hashmap的區別(即1.8做了哪些優化)?

1.為了加快查詢效率,java8的hashmap引入了紅黑樹結構,當陣列長度大於預設閾值64時,且當某一連結串列的元素>8時,該連結串列就會轉成紅黑樹結構,查詢效率更高。(問題來了,什麼是紅黑樹?什麼是B+樹?(mysql索引有B+樹索引)什麼是B樹?什麼是二叉查詢樹?)資料結構方面的知識點會更新在【資料結構專題】,這裡不展開。

這裡只簡單的介紹一下紅黑樹:

紅黑樹是一種自平衡二叉樹,擁有優秀的查詢和插入/刪除效能,廣泛應用於關聯陣列。對比AVL樹,AVL要求每個結點的左右子樹的高度之差的絕對值(平衡因子)最多為1,而紅黑樹通過適當的放低該條件(紅黑樹限制從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長,結果是這個樹大致上是平衡的),以此來減少插入/刪除時的平衡調整耗時,從而獲取更好的效能,而這雖然會導致紅黑樹的查詢會比AVL稍慢,但相比插入/刪除時獲取的時間,這個付出在大多數情況下顯然是值得的。好了我知道你們看暈了,移步去看看我的【資料結構專題】吧。

2.優化擴容方法,在擴容時保持了原來連結串列中的順序,避免出現死迴圈

12:JDK1.7的concurrenthashmap和JDK1.8又有什麼區別?

1.8的實現已經拋棄了Segment分段鎖機制,利用Node陣列+CAS+Synchronized來保證併發更新的安全,底層採用陣列+連結串列+紅黑樹的儲存結構。

java給我們帶來了併發安全的ConcurrentHashMap,它的實現是依賴於 Java 記憶體模型,所以我們在瞭解 ConcurrentHashMap 的之前必須瞭解一些底層的知識:

java記憶體模型

java中的Unsafe

java中的CAS

java同步器AQS

ReentrantLock

所以在這裡我不準備深入講解ConcurrentHashMap ,我會在【併發程式設計】專題通過一步步詳解併發基礎,從java記憶體模型,synchronized,volatile,Unsafe到CAS,AQS,各種鎖再到JUC併發包相關。

先放張java記憶體模型的思維導圖勾引一波,光java記憶體模型一個點就有這麼多要講的了。

13:那麼問題來了,什麼是CAS?

關於CAS方面的知識點,又會涉及到ABA問題,又可以扯到樂觀鎖悲觀鎖,鎖程式設計,AQS等,相關內容將更新在【併發程式設計專題】,這裡不做展開

14:那1.9的呢?

瞄了一眼,好像和1.8的沒啥區別,這裡不做展開....(別打臉)


相關文章