1、HashMap 底層的數據結構
數組 + 最簡單的原理
對張三計算出來一個hash值,根據這個hash值對數組進行取模,就會定位到數組裏的一個元素中去
[<>,<>,<>,<>,<張三,測試數據>,<>,<>,<李四,測試數據>,<>,<>,<>,<>,<>,<>,<>,<>,]
假設可以放16個元素,取模,index
array[4] = <張三,測試數據>
map.get("張三") -> hash值 -> 對數組長度進行取模 -> return array[4]
2、jdk1.8中對 hash算法 和 尋址算法 的優化
map.get("張三") -> hash值 -> 對數組長度進行取模 -> return array[4]
對於這個取模是有一定的優化的
hash 算法優化
下面爲jdk1.8 以後的HashMap的一段源碼
static final int hash (Object key) {
int h;
return (key == null) ? 0 : (h == key.hashCode()) ^ (h >>> 16);
}
比如說:有一個key的hash值
1111 1111 1111 1111 1111 1010 0111 1100
^ 0000 0000 0000 0000 1111 1111 1111 1111 (右移16位)
1111 1111 1111 1111 0000 0101 1000 0011 -> int 值,32位
意義必須和尋址算法結合,並不是簡單的對數組長度取模,定位到數組的一個位置,塞進去就行了,並不是
hash尋址算法優化
高低16位都參與運算
hash & (n - 1 ) -> 數組裏的一個位置
1111 1111 1111 1111 1111 1010 0111 1100 (沒有經過優化的 hash 值)
0000 0000 0000 0000 0000 0000 0000 1111
爲了避免出現hash衝突,將經過hash優化的hash值(高低十六位異或運算),與n-1進行&運算,此時處理的hash值的低16位包含,高低16位的特徵,避免出現hash衝突
1111 1111 1111 1111 0000 0101 1000 0011 -> int 值,32位
0000 0000 0000 0000 0000 0000 0000 1111
得出的效果也是數組中的位置
取模運算,性能相對比較差,爲了優化這個數組尋址的過程
hash & (n - 1) -> 效果是跟 hash 對 n 取模,效果是一樣的, 但是與運算的性能要比hash對n取模要高很多,數學公式,數組的長度一直是2 的n次方, 只要保持數組長度是 2 的 n次方
hash 對 n 取模的效果 -> hash & (n - 1) 效果是一樣的,後者的性能更高
hash 值一樣 -> 就會在數組中的同一個位置,就需要進行復雜的 hash 衝突的處理
總結:
hash 算法的優化: 對每個hash值,在它的低 16 位中,讓高低16位進行了異或,讓它的低16位同時保持了高低 16 位的特徵,儘量避免一些 hash 值後續出現衝突,大家可能會進入數組的同一個位置
尋址算法的優化: 用與運算替代取模,提升性能
3、HashMap 如何解決hash 碰撞問題
hash 衝突問題,鏈表 + 紅黑樹 O(n) 和 O(logn)
map.put 和 map.get -> hash 算法優化 (避免 hash 衝突),尋址性能優化
算出 key 的 hash 值,到數組中尋址,找到一個位置,把key-value 對放進數組,或者從數組中取出來
兩個 key ,多個 key, 它們算出來的hash 值,與n-1,與運算之後,發現定位出來的數組的位置還是一樣的,hash碰撞,hash衝突
衝突解決:[<> -> <> -> <>] 會在衝突的位置掛一個鏈表,這個鏈表裏面放入多個元素,讓多個key - value 對,同時放在數組的一個位置裏
get, 如果定位到數組裏發現這個位置掛了一個鏈表,此時遍歷鏈表從裏面找到自己要找的那個key - value 對就可以了
假設鏈表很長, 可能會導致遍歷鏈表,性能會比較差,O(n)
優化: 如果鏈表的長度達到了一定的長度之後,其實會把鏈表轉爲紅黑樹,遍歷一顆紅黑樹查找一個元素,算法複雜度 O(logn)
性能會比鏈表高一些
4、hashMap 擴容
底層是一個數組,當這個數組滿了之後,就會自動擴容,變成一個更大的數組,讓在裏面可以去放更多的元素
2倍擴容
擴容影響:位置可能改變,需要重新對每個 hash 值進行尋址,也就是需要通過16位的hash值,與擴容後的32 -1 進行&運算,重新計算hash值
原數組:
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash1 1111 1111 1111 1111 0000 1111 0000 0101
&結果 0000 0000 0000 0000 0000 0000 0000 0101 = 5 (index = 5的位置)
擴容後數組:
n-1 0000 0000 0000 0000 0000 0000 0001 1111
hash1 1111 1111 1111 1111 0000 1111 0001 0101
&結果 0000 0000 0000 0000 0000 0000 0001 0101 = 21 (index = 21的位置)
判斷二進制結果中是否多出一個bit 的 1, 如果沒多,那麼就是原來的index,如果多了出來,那麼就是 index + oldCap, 通過這個方式,就避免了rehash 的時候,用每個 hash 對新數組.length 取模,取模性能不高,位運算的性能比較高