打個小guang告,搜索拼duoduo店鋪: Boush雜貨鋪
物美價廉,你值得擁有
jdk7、jdk8 hashmap、concurrenthashmap以及他們的區別
一、hashmap1.7
1.數據結構
也稱爲哈希數組,默認數組大小是16,每對key-value鍵值對其實是存在map的內部類entry裏的,如果不同的key映射到了數組的同一位置處,就會採用頭插法將其放入單鏈表中。
2.hashmap原理(put、get)
2.1.put原理
1.根據key獲取對應hash值:int hash = hash(key.hashcode())
2.根據hash值和數組長度確定對應數組位置 int i = indexFor(hash, table.length); 簡單理解就是i = hash值%模以 數組長度(其實是按位與運算)。如果不同的key都映射到了數組的同一位置處,就將其放入單鏈表中。且新來的是放在頭節點,即投頭插法。
2.2.get原理
1.通過hash獲得對應數組位置,遍歷該數組所在鏈表(key.equals())
3.hashcode衝突怎麼辦?爲什麼這麼處理
1.採用頭插法,直接放在鏈表的頭部
2.因爲HashMap的作者認爲,後插入的Entry被查找的概率大些,所以放在頭部(因爲get())查詢的時候會遍歷整個鏈表)
4.hashmap的默認數組長度是多少?爲什麼?
16,是爲了服務於從key映射到index的hash算法(看下面)。
5.hashmap達到默認負載因子(0.75)怎麼辦?爲啥要16或者2的冪次?
1.自動雙倍擴容,擴容後重新計算每個鍵值對位置。且長度必須爲16或者2的冪次
2.若不是16或者2的冪次,位運算的結果不夠均勻分佈,顯然不符合Hash算法均勻分佈的原則。反觀長度16或者其他2的冪,Length-1的值是所有二進制位全爲1,這種情況下,index的結果等同於HashCode後幾位的值。只要輸入的HashCode本身分佈均勻,Hash算法的結果就是均勻的。
6.hashmap是線程安全的麼?爲什麼?
不是線程安全的,因爲沒有枷鎖
7.併發會導致什麼問題?
hashmap在接近臨界點時,若此時兩個或者多個線程進行put操作,都會進行resize(擴容)和ReHash(爲key重新計算所在位置),而ReHash在併發的情況下可能會形成鏈表環。在執行get的時候,會觸發死循環,引起CPU的100%問題。
注:jdk8已經修復hashmap這個問題了,jdk8中擴容時保持了原來鏈表中的順序,即put不再採用頭插法,而是尾插法。但是HashMap仍是非併發安全,在併發下,還是要使用ConcurrentHashMap。
8.hashmap和hashtable的區別
兩者的區別
兩者的區別
不安全更高16key-value都允許hashtable安全略低11不允許(拋異常)
\ | 線程 | 效率 | null值 | 數組默認值 | 擴容 | 繼承自 |
---|---|---|---|---|---|---|
hashmap | 不安全 | 更高 | key-value都允許null | 16 | 16*0.75 = 12,當前個數大於等於閾值,則擴容爲原來的2倍 | AbstractMap |
hashtable | 安全 | 略低 | 不允許(拋異常) | 11 | 11*0.75=12,擴容爲原來的2倍+1 | Dictionary |
9.爲啥hashtable不允許key、value爲null
1.這是因爲Hashtable使用的是安全失敗機制(fail-safe),這種機制會使你此次讀到的數據不一定是最新的數據。
如果你使用null值,就會使得其無法判斷對應的key是不存在還是爲空,因爲你無法再調用一次contain(key)來對key是否存在進行判斷,調用contain方法時hashtable有可能已經被更新過了。
10.安全失敗(fail-safe)、快速失敗(fail-fase)
hashtable使用的迭代器是Enumerator,是安全失敗(fail-safe)機制
hashmap使用的是Iterator迭代器,是快速失敗(fail-fast)機制,當其他線程改變了HashMap 的結構,如:增加、刪除元素,將會拋出ConcurrentModificationException 異常,而 Hashtable則不會。
二、hashmap1.8
1.jdk1.8做了哪些優化?
1.爲了加快查詢效率,java8的hashmap引入了紅黑樹結構,當數組長度大於默認閾值64時,且當某一鏈表的元素>8時,該鏈表就會轉成紅黑樹結構,查詢效率更高。(問題來了,什麼是紅黑樹?什麼是B+樹?(mysql索引有B+樹索引)什麼是B樹?什麼是二叉查找樹?)數據結構方面的知識點這裏不展開。
這裏只簡單的介紹一下紅黑樹:
紅黑樹是一種自平衡二叉樹,擁有優秀的查詢和插入/刪除性能,廣泛應用於關聯數組。對比AVL樹,AVL要求每個結點的左右子樹的高度之差的絕對值(平衡因子)最多爲1,而紅黑樹通過適當的放低該條件(紅黑樹限制從根到葉子的最長的可能路徑不多於最短的可能路徑的兩倍長,結果是這個樹大致上是平衡的),以此來減少插入/刪除時的平衡調整耗時,從而獲取更好的性能,而這雖然會導致紅黑樹的查詢會比AVL稍慢,但相比插入/刪除時獲取的時間,這個付出在大多數情況下顯然是值得的。
2.優化擴容方法,在擴容時保持了原來鏈表中的順序,避免出現死循環,插入採用尾插法,而不是頭插法
三、concurrenthashmap1.7
1.數據結構
Segment + entry
理解:hashmap是有entry數組組成,而concurrenthashmap則是Segment數組組成。而Segment又是什麼呢?Segment本身就相當於一個HashMap。同HashMap一樣,Segment包含一個HashEntry數組,數組中的每一個HashEntry既是一個鍵值對,也是一個鏈表的頭節點。
像這樣的Segment對象,在ConcurrentHashMap集合中有多少個呢?有2的N次方個,共同保存在一個名爲segments的數組當中。
可以說,ConcurrentHashMap是一個二級哈希表。在一個總的哈希表下面,有若干個子哈希表。(這樣類比理解多個hashmap組成一個cmap)
2.put、get方法
1.Put方法:
1.爲輸入的Key做Hash運算,得到hash值。
2.通過hash值,定位到對應的Segment對象
3.獲取可重入鎖
4.再次通過hash值,定位到Segment當中數組的具體位置。
5.插入或覆蓋HashEntry對象。
6.釋放鎖。
2.Get方法:
1.爲輸入的Key做Hash運算,得到hash值。
2.通過hash值,定位到對應的Segment對象
3.再次通過hash值,定位到Segment當中數組的具體位置。
由此可見,和hashmap相比,ConcurrentHashMap在讀寫的時候都需要進行二次定位。先定位到Segment,再定位到Segment內的具體數組下標。
四、concurrenthashmap1.8
1.8的實現已經拋棄了Segment分段鎖機制,利用Node數組+CAS+Synchronized來保證併發更新的安全,底層採用數組+鏈表+紅黑樹的存儲結構。
針對 synchronized 獲取鎖的方式,jdk1.8中JVM使用了鎖升級的優化方式,效率比之前高很多,就是先使用偏向鎖優先同一線程再次獲取鎖,如果失敗,就升級爲CAS輕量級鎖,如果失敗就會短暫自旋,防止線程被系統掛起。最後如果以上都失敗就升級爲重量級鎖。
引用地址:
https://mp.weixin.qq.com/s/UM21yeO242d-EVy8EL5Xww
https://mp.weixin.qq.com/s/AixdbEiXf3KfE724kg2YIw