2019.6.27 《程序員小灰》HashMap總結

1.HashMap

HashMap用於存儲鍵值對的集合,這些鍵值對分散存儲在一個數組中。

1.put方法:需要一個哈希函數,來確定鍵值對插入的位置。可能會出現衝突!

怎麼辦?我們可以利用鏈表來解決,數組的每一個元素不止是一個鍵值對,也是一個鏈表的頭結點。

2.Get方法:由於有衝突,同一位置可能匹配到多個Entry,這時候就需要順着頭結點,一個一個向下來查找。

3.默認的初始長度?爲什麼?

答:16。每次自動擴展或是手動初始化時,長度必須是2的冪

如何實現一個儘量均勻分佈的Hash函數呢?我們通過利用Key的HashCode值來做某種運算:進行位運算,有如下的公式(Length是HashMap的長度):

index =  HashCode(Key) &  (Length - 1) 。Hash算法最終得到的index結果,完全取決於Key的Hashcode值的最後幾位。當HashMap長度爲10的時候,有些index結果的出現機率會更大,而有些index結果永遠不會出現(比如0111)

2.Rehash

hashMap的容量有限,經過多次元素插入,使得衝突逐漸提高,這是,我們進行擴展長度,即Resize

步驟:

1..高併發下,爲什麼HashMap可能會出現死鎖?

假設一個hashMAp已經到了Resize的臨界點,此時有兩個線程AB,在同一時刻對HashMap進行put操作,鏈表可能會出現環形

此時在使用get,由於帶有環形鏈表,所以程序會進入死循環!

爲了杜絕這種情況的發生,我們通常使用另一個集合類ConcurentHashMap,這個集合兼顧了線程安全和性能。

3.如何判斷鏈表有環?

方法一:深度遍歷節點,記錄下來,發現有重複的,即是有環

方法二:創建兩個指針1和2(在java裏就是兩個對象引用),同時指向這個鏈表的頭節點。然後開始一個大循環,在循環體中,讓指針1每次向下移動一個節點,讓指針2每次向下移動兩個節點,然後比較兩個指針指向的節點是否相同。如果相同,則判斷出鏈表有環,如果不同,則繼續下一次循環。

假設從鏈表頭節點到入環點的距離是D,鏈表的環長是S。那麼循環會進行S次(爲什麼是S次,有心的同學可以自己揣摩下),可以簡單理解爲O(N)。除了兩個指針以外,沒有使用任何額外存儲空間,所以空間複雜度是O(1)。

4.引申問題:

Q1:判斷兩個單向鏈表是否相交,如果相交,求出交點。

 

Q2:在一個有環鏈表中,如何找出鏈表的入環點?

設置一個指針,走一圈,記錄每個節點,出現重複的,即是入環點

5.什麼是ConcurrentHashMap?

首先,HashMap不是線程安全的,在併發插入元素的時候,有可能出現帶環鏈表,讓下一次讀操作出現死循環。

1.併發場景下,ConcurrentHaspMap是怎麼保證線程安全的?怎麼實現高性能讀寫?

想要避免HashMap的線程安全問題,可以改用HashTable或者Collections.synchronizedMap

但是兩者性能都不高,無論是讀還是寫,它們都會給整個集合加鎖,導致同一時間的其他操作爲之阻塞。

這時候,兼顧安全和效率,concurrentHashMap應運而生

2.Segment

Segment本身就相當於一個HashMap對象。同HashMap一樣,Segment包含一個HashEntry數組,數組中的每一個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。

可以說,ConcurrentHashMap是一個二級哈希表。在一個總的哈希表下面,有若干個子哈希表。這樣的二級結構,和數據庫的水平拆分有些相似。

3.concurrentHashMap採用了鎖分段技術,每一個segment就好比一個自治區,讀寫高度自治,segment之間互不影響。

Case1:不同Segment的併發寫入:可以

Case2:同一Segment的一寫一讀:可以

Case3:同一Segment的併發寫入:上鎖,不能併發寫入,會阻塞

4.concurrentHashMap的讀寫詳細過程:

ConcurrentHashMap在對Key求Hash值的時候,爲了實現Segment均勻分佈,進行了兩次Hash。

Get方法:

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

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

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

 

Put方法:

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

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

3.獲取可重入鎖

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

5.插入或覆蓋HashEntry對象。

6.釋放鎖。

6.ConcurrentHashMap的Size方法

ConcurrentHashMap的Size方法是一個嵌套循環,大體邏輯如下:

1.遍歷所有的Segment。

2.把Segment的元素數量累加起來。

3.把Segment的修改次數累加起來。

4.判斷所有Segment的總修改次數是否大於上一次的總修改次數。如果大於,說明統計過程中有修改,重新統計,嘗試次數+1;如果不是。說明沒有修改,統計結束。

5.如果嘗試次數超過閾值,則對每一個Segment加鎖,再重新統計。

6.再次判斷所有Segment的總修改次數是否大於上一次的總修改次數。由於已經加鎖,次數一定和上次相等。

7.釋放鎖,統計結束。

 

爲了儘量不鎖住所有Segment,首先樂觀地假設Size過程中不會有修改。當嘗試一定次數,才無奈轉爲悲觀鎖,鎖住所有Segment保證強一致性。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章