HashMap 擴容、尋址、hash算法

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 取模,取模性能不高,位運算的性能比較高

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章