衆所周知HashMap(無序,key不能夠重複)的底層是數組+鏈表實現的。可是在jdk1.7和jdk1.8中稍有不一樣。java
Base 1.7數組
其中有兩個重要的參數:安全
容量的默認大小是 16,負載因子是 0.75,當 HashMap
的 size > 16*0.75
時就會發生擴容(容量和負載因子均可以自由調整)。而擴容這個過程涉及到 rehash、複製數據等操做,因此很是消耗性能。所以一般建議能提早預估 HashMap 的大小最好,儘可能的減小擴容帶來的性能損耗。併發
首先會將傳入的 Key 作
hash
運算計算出 hashcode,而後根據數組長度取模計算出在數組中的 index 下標。性能因爲在計算中位運算比取模運算效率高的多,因此 HashMap 規定數組的長度爲
2^n
。這樣用2^n - 1
作位運算與取模效果一致,而且效率還要高出許多。優化因爲數組的長度有限,因此不免會出現不一樣的 Key 經過運算獲得的 index 相同,這種狀況能夠利用鏈表來解決,HashMap 會在
table[index]
處造成鏈表,採用頭插法將數據插入到鏈表中。spa
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
是一個不容許存儲重複元素的集合,它的實現比較簡單,只要理解了HashMap
,HashSet
就水到渠成了。HashSet全部原理基本都是基於HashMap實現的
LinkedHashMap是有序的。
它對HashMap
進行了拓展,使用了雙向鏈表來保證了順序性。