jdk1.8hashmap为什么对hash进行了一次扰动处理
我們一步一步來分析
- 第1步:h = key.hashCode()
-
第2步:h >>> 16
無符號右移(>>>):
對于正數的帶符號右移,不論正數還是負數,移位過程中高位均補零。
-
第3步:h ^ (h >>> 16)
- 第四步:計算元素在數組中存放的位置
由下面這行代碼決定的:
我們將上面這步操作作為第4步操作,來對比一下執行1、2、3、4四個步驟和只執行第1、4兩個步驟所產生的不同效果。
我們向hashmap中put兩個元素node1(key1, value1)、node2(key2, value2),hashmap的數組長度n=16。
執行1、2、3、4 四個步驟:
假設計算的結果為:h = 3654061296
對應的二進制數為: ???01101100 11100110 10001100 11110000
h無符號右移16位得到: 00000000 00000000 01101100 11100110
異或操作后得到hash:? 01101100 11110000 11100000 00000110
n-1=15 對應二進制數 : ?? 00000000 00000000 00000000 00001111
hash : ??01101100 11110000 11100000 00000110
hash & 15 : ?? 00000000 00000000 00000000 00000110
轉化為10進制 : &ensp 5
最終得到i的值為5,也就是說node1存放在數組索引為5的位置。
同理我們對(key2, value2) 進行上述同樣的操作過程:
假設計算的結果為:h = 3652881648
對應的二進制數為: ???01101100 11011101 10001100 11110000
h無符號右移16位得到: 00000000 00000000 01101100 11011101
異或操作后得到hash:? 01101100 11110000 11100000 00101101
n-1=15 對應二進制數 : ?? 00000000 00000000 00000000 00001111
hash : ??01101100 11110000 11100000 00101101
hash & 15 : ??00000000 00000000 00000000 00001101
轉化為10進制 : &ensp 13
最終得到i的值為13,也就是說node2存放在數組索引為13的位置
執行1、4兩個步驟:
計算的結果同樣為:h = 3654061296
對應的二進制數為: ???01101100 11100110 10001100 11110000
n-1=15 對應二進制數 : ?? 00000000 00000000 00000000 00001111
hash(h) : ??01101100 11100110 10001100 11110000
hash & 15 : ??00000000 00000000 00000000 00000000
轉化為10進制 : ? 0
最終得到i的值為0,也就是說node1存放在數組索引為0的位置
同理我們對(key2, value2) 進行上述同樣的操作過程:
計算的結果同樣為:h = 3652881648
對應的二進制數為: ???01101100 11011101 10001100 11110000
n-1=15 對應二進制數 : ?? 00000000 00000000 00000000 00001111
hash(h) : ??01101100 11110000 11100000 11110000
hash & 15 : ??00000000 00000000 00000000 00000000
轉化為10進制 : ? 0
最終得到i的值為0,也就是說node2同樣存放在數組索引為0的位置
相信大家已經看出區別了:
????當數組長度n較小時,n-1的二進制數高16位全部位0,這個時候如果直接和h值進行&(按位與)操作,那么只能利用到h值的低16位數據,這個時候會大大增加hash沖突發生的可能性,因為不同的h值轉化為2進制后低16位是有可能相同的,如上面所舉例子中:key1.hashCode() 和key2.hashCode() 得到的h值不同,一個h1 = 3654061296 ,另一個h2 = 3652881648,但是不幸的是這h1、h2兩個數轉化為2進制后低16位是完全相同的,所以h1 & (n-1)和 h2 & (n-1) 會計算出相同的結果,這也導致了node1和node2 存儲在了數組索引相同的位置,發生了hash沖突。
? 當我們使用進行 h ^ (h >>> 16) 操作時,會將h的高16位數據和低16位數據進行異或操作,最終得出的hash值的高16位保留了h值的高16位數據,而hash值的低16數據則是h值的高低16位數據共同作用的結果。所以即使h1和h2的低16位相同,最終計算出的hash值低16位也大概率是不同的,降低了hash沖突發生的概率。
? ps:這里面還有一個值的注意的點: 為什么是(n-1)?
我們知道n是hashmap中數組的長度,那么為要進行n-1的操作?答案同樣是為了降低hash沖突發生的概率!
要理解這一點,我們首先要知道HashMap規定了數組的長度n必須為2的整數次冪,至于為什么是2的整數次冪,會在HashMap的擴容方法resize()里詳細講。
既然n為2的整數次冪,那么n一定是一個偶數。那么我們來比較i = hash & n和 i = hash & (n-1)有什么異同。
n為偶數,那么n轉化為2進制后最低位一定為0,與hash進行按位與操作后最低位仍一定為0,這就導致i值只能為偶數,這樣就浪費了數組中索引為奇數的空間,同時也增加了hash沖突發生的概率。
所以我們要執行n-1,得到一個奇數,這樣n-1轉化為二進制后低位一定為1,與hash進行按位與操作后最低位即可能位0也可能位1,這就是使得i值即可能為偶數,也可能為奇數,充分利用了數組的空間,降低hash沖突發生的概率。
總結
以上是生活随笔為你收集整理的jdk1.8hashmap为什么对hash进行了一次扰动处理的全部內容,希望文章能夠幫你解決所遇到的問題。