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保證強一致性。