1. 程式人生 > >JDK1.8後的HashMap底層結構變化

JDK1.8後的HashMap底層結構變化

JDK1.8以後 HashMap的資料結構發生了一些改變,從單純的陣列加連結串列結構變成陣列+連結串列+紅黑樹.

其中Node是HashMap的一個內部類,實現Map.Entry介面,本質是一個KV對映,上圖中每個元素都是一個Node物件. 
HashMap顧名思義是通過Hash表進行儲存.為了解決雜湊碰撞的問題,Java採用這種陣列 + 連結串列方式來進行儲存. 
具體的put方法原始碼如下. 

當JVM儲存HashMap的K-V時,僅僅通過Key來決定每一個Entry的儲存槽位(Node[]中的index).並且Value以連結串列的形式掛載到對應槽位上即可(1.8之後如果長度大於8則轉為紅黑樹). 
HashMap之所以稱之為HashMap是因為HashMap在put(String,Object)的時候JVM會對存入的物件進行一次hash(所有物件都是繼承Object,而hashcode方法來自Object類中),從而獲取到這個物件的hash值,接著JVM就根據這個hash值來決定該元素的儲存位置. 
比如 我使用map.put(“UNSC”,”Cortana”);對HashMap中存入對應的KV,先將Key通過hashcode()方法獲取到雜湊值,再通過雜湊演算法的高位運算和取模運算來確定這個Key儲存的槽位

如果發生兩個Key儲存到了同一個位置,則發生了Hash衝突(碰撞),Java採用的陣列 + 連結串列方式就發揮作用了.Java採用鏈地址法(雜湊值相同的元素構成一個連結串列,連結串列頭指標指向Node[]的index),避免了Hash衝突的問題(參考上面的HashMap的圖).Hash衝突發生後,這個槽位中儲存的不是一個Entry而是多個Entry,此時就使用到了Entry連結串列(參見HashMap資料結構).JVM是按照順序去遍歷每一個Entry,一直到查詢到對應的Entry為止(連結串列查詢).在上圖的for迴圈當中可以看到,如果hashcode相同,發生了hash衝突,新存入的值會覆蓋舊的值,並且將舊的值返回

.

HashMap擴容機制 
HashMap中有resize(),當HashMap的元素個數超過陣列的容量(length),進行擴容,預設情況下陣列容量是16,當HashMap中的元素個數超過12個時(16*0.75 == 12),超過了臨界值(就是原始碼中的threshold),需要把陣列大小擴容一倍,然後通過rehash(再雜湊),重新計算每個元素在陣列中的位置.

HashMap執行緒不安全的原因 

ashMap在使用put方法時會呼叫這個方法,具體為addEntry(hash, key, value, i); 
此時如果有兩個執行緒T1和T2,兩個執行緒同時對一個數組位置呼叫addEntry方法,T1和T2都能獲得相同槽位(bucketIndex)的Node。