基於原始碼的Java集合框架學習⑭ Map總結
Map概括
Map 是“鍵值對”對映的抽象介面。
AbstractMap 實現了Map中的絕大部分函式介面。它減少了“Map的實現類”的重複編碼。
SortedMap 有序的“鍵值對”對映介面。
NavigableMap 是繼承於SortedMap的,支援導航函式的介面。
HashMap 是基於“拉鍊法”實現的散列表。一般用於單執行緒程式中。
Hashtable 也是基於“拉鍊法”實現的散列表。它一般用於多執行緒程式中。
WeakHashMap 也是基於“拉鍊法”實現的散列表,它一般也用於單執行緒程式中。相比HashMap,WeakHashMap中的鍵是“弱鍵”,當“弱鍵”被GC回收時,它對應的鍵值對也會被從WeakHashMap中刪除;而HashMap中的鍵是強鍵。
TreeMap 是有序的散列表,它是通過紅黑樹實現的。它一般用於單執行緒中儲存有序的對映。
IdentityHashMap在判斷鍵(和值)時使用引用相等性,所有的key和value都儲存到Object[]陣列table中,並且key和value相鄰儲存,當出現雜湊衝突時,會往下遍歷陣列,直到找到一個空閒的位置。
HashMap的底層實現
JDK1.8 之前 HashMap 底層是 陣列和連結串列 結合在一起使用也就是 連結串列雜湊。HashMap 通過 key 的 hashCode 經過擾動函式處理過後得到 hash 值,然後通過 (n - 1) & hash 判斷當前元素存放的位置(這裡的 n 指的是陣列的長度),如果當前位置存在元素的話,就判斷該元素與要存入的元素的 hash 值以及 key 是否相同,如果相同的話,直接覆蓋,不相同就通過拉鍊法解決衝突。
JDK1.8之後在解決雜湊衝突時有了較大的變化,當連結串列長度大於閾值(預設為8)時,將連結串列轉化為紅黑樹,以減少搜尋時間。
HashMap 的長度為什麼是2的冪次方
為了能讓 HashMap 存取高效,儘量較少碰撞,也就是要儘量把資料分配均勻。Hash 值的範圍值-2147483648到2147483648,前後加起來大概40億的對映空間,只要雜湊函式對映得比較均勻鬆散,一般應用是很難出現碰撞的。但問題是一個40億長度的陣列,記憶體是放不下的。所以這個雜湊值是不能直接拿來用的。用之前還要先做對陣列的長度取模運算,得到的餘數才能用來要存放的位置也就是對應的陣列下標。這個陣列下標的計算方法是“ (n - 1) & hash ”。(n代表陣列長度)。
“取餘(%)操作中如果除數是2的冪次則等價於與其除數減一的與(&)操作(也就是說 hash%length==hash&(length-1)的前提是 length 是2的 n 次方;)。” 並且採用二進位制位操作 &,相對於%能夠提高運算效率,這就解釋了 HashMap 的長度為什麼是2的冪次方。
HashMap 多執行緒操作導致死迴圈問題
在多執行緒下,進行 put 操作會導致 HashMap 死迴圈,原因在於 HashMap 的擴容 resize()方法。由於擴容是新建一個數組,複製原資料到陣列。由於陣列下標掛有連結串列,所以需要複製連結串列,但是多執行緒操作有可能導致環形連結串列。
HashMap和Hashtable的比較
1.底層結構:它們都是採用拉鍊法實現的。儲存的思想都是:通過table陣列儲存,陣列的每一個元素都是一個Entry;而一個Entry就是一個單向連結串列,Entry連結串列中的每一個節點就儲存了key-value鍵值對資料。
2. 執行緒安全:Hashtable的幾乎所有函式都是同步的,即它是執行緒安全的,支援多執行緒。而HashMap的函式則是非同步的,它不是執行緒安全的。
3. null相關:HashMap的key、value都可以為null。Hashtable的key、value都不可以為null。
4. 容量:Hashtable預設的初始大小為11,之後每次擴充,容量變為原來的2n+1。HashMap預設的初始化大小為11,之後每次擴充,容量變為原來的2倍。