Map 中 put 和 get 的優化
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
舉個例子
key="ykx"
key的hashCode是=119718 這是個10進制數
原始hashCode對應二進制11101001110100110==00000000000000011101001110100110
然後我再向右位移16位後的二進制==00000000000000000000000000000001
異或運算
0000000000000001 1101001110100110
0000000000000000 0000000000000001
0000000000000001 1101001110100111==119719 這個就是最終的hash值
優化點在哪
HashMap在get方法去獲取值時是這樣的
hash(key)&(n-1) 進行與運算而找到數組位置
0000000000000001 1101001110100111 上面優化後的hash值 hashMap默認數組長度16
0000000000000000 0000000000001111 &
0000000000000000 0000000000000111 =7
這就是最終的位置下標 只要數組長度是2的n次方 你會發現:119719%16與119719&(16-1)的值時相同的
因爲與運算的性能會比取模效率高。
總結
其實這裏最重要的幾個點就是在算hash值時高是16位與低16進行了異或運算(因爲很有可能有兩個不同的值hash高16位不相同低16位相同)。這樣就導致hashmap尋址運算時低16位已經包含了高16位與低16 的特徵,因爲在尋址的時候大多數都是低16位在運算,因爲數組長度-1的數字大小一般情況都比較小。所以在get尋址時基本都是低16在運算,儘量避免hash衝突,尋址時用與運算代替取模運算也是比較大的優化,只要當hashmap的數組長度是2的n次方那麼我們算出來的hash值取模這個長度等於與運算這個長度-1 的值。。
如果說衝突了會怎樣
當hash衝突時會在當前下標建立一個鏈表來維護。jdk爲了維護在遍歷時性能夠好,當鏈表長度大於8的時候迴轉化爲紅黑樹,這時查詢的時間複雜度就是O(logn) 。
爲什麼是8而不是4:我的理解是log2(8)=3而log2(4)=2 由於是紅黑樹、B+tree等 一般都是3層性能就是折中方案。
HashMap擴容後怎麼重新定義位置
默認是16長度,以後每次擴容時 都會重新遍歷原來的值拿到hash與 新的數組長度-1 進行與運算放到新的位置。