HashMap底層分析

衆所周知HashMap(無序,key不能夠重複)的底層是數組+鏈表實現的。可是在jdk1.7和jdk1.8中稍有不一樣。java

Base 1.7數組

其中有兩個重要的參數:安全

  • 容量
  • 負載因子

容量的默認大小是 16,負載因子是 0.75,當 HashMap 的 size > 16*0.75 時就會發生擴容(容量和負載因子均可以自由調整)。而擴容這個過程涉及到 rehash、複製數據等操做,因此很是消耗性能。所以一般建議能提早預估 HashMap 的大小最好,儘可能的減小擴容帶來的性能損耗併發

put 方法

首先會將傳入的 Key 作 hash 運算計算出 hashcode,而後根據數組長度取模計算出在數組中的 index 下標。性能

因爲在計算中位運算比取模運算效率高的多,因此 HashMap 規定數組的長度爲 2^n 。這樣用 2^n - 1 作位運算與取模效果一致,而且效率還要高出許多。優化

因爲數組的長度有限,因此不免會出現不一樣的 Key 經過運算獲得的 index 相同,這種狀況能夠利用鏈表來解決,HashMap 會在 table[index]處造成鏈表,採用頭插法將數據插入到鏈表中。spa

get 方法

get 和 put 相似,也是將傳入的 Key 計算出 index ,若是該位置上是一個鏈表就須要遍歷整個鏈表,經過 key.equals(k) 來找到對應的元素。由於每次根據 key 的 hashcode 映射到 Entry 數組上,因此hashmap是無序的。線程

遍歷方式

Iterator<Map.Entry<String, Integer>> entryIterator = map.entrySet().iterator();
        while (entryIterator.hasNext()) {
            Map.Entry<String, Integer> next = entryIterator.next();
            System.out.println("key=" + next.getKey() + " value=" + next.getValue());
        }
Iterator<String> iterator = map.keySet().iterator();
        while (iterator.hasNext()){
            String key = iterator.next();
            System.out.println("key=" + key + " value=" + map.get(key));

        }
map.forEach((key,value)->{
    System.out.println("key=" + key + " value=" + value);
});

強烈建議使用第一種 EntrySet 進行遍歷。code

第一種能夠把 key value 同時取出,第二種還得須要經過 key 取一次 value,效率較低, 第三種須要 JDK1.8 以上,經過外層遍歷 table,內層遍歷鏈表或紅黑樹。get

Base 1.8

在 JDK1.8 中對 HashMap 進行了優化: 當 hash 碰撞以後寫入鏈表的長度超過了閾值(默認爲8)而且 table 的長度不小於64(不然擴容一次)時,鏈表將會轉換爲紅黑樹

假設 hash 衝突很是嚴重,一個數組後面接了很長的鏈表,此時從新的時間複雜度就是 O(n) 。

若是是紅黑樹,時間複雜度就是 O(logn) 。

大大提升了查詢效率。

簡單總結下 HashMap:不管是 1.7 仍是 1.8 其實都能看出 JDK 沒有對它作任何的同步操做,因此併發會出問題(既線程不安全的),併發操做容易在一個桶上造成環形鏈表;這樣當獲取一個不存在的 key 時,計算出的 index 正好是環形鏈表的下標就會出現死循環

所以 JDK 推出了專項專用的 ConcurrentHashMap ,該類位於 java.util.concurrent 包下,專門用於解決併發問題。

想要深刻了解的小夥伴能夠自行參考:HashMap? ConcurrentHashMap? 相信看完這篇沒人能難住你!

 

擴展

HashSet是一個不容許存儲重複元素的集合,它的實現比較簡單,只要理解了HashMapHashSet就水到渠成了。HashSet全部原理基本都是基於HashMap實現的

LinkedHashMap是有序的。它對HashMap進行了拓展,使用了雙向鏈表來保證了順序性。

相關文章
相關標籤/搜索