大家都知道(jdk1.8)HashMap中計算數組下標是HashMap的核心算法。小編今天在看HashMap源碼中看到了hash(Object key)方法百思不得其解。小編問百度,查找相關博客,甚至連HashMap的關於hash(Object key)英文解釋都看了。但是都只是說了為了盡量均勻,沒有詳細講。小編今天為大家詳細講解一下這兩個問題。
HashMap中hash(Object key)的原理,為什么這樣做?
先看下hash(Object key)方法,詳細大家基本都能看懂,但是知道這一步(h = key.hashCode()) ^ (h >>> 16)原因的人很少。
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
首先這個方法的返回值還是一個哈希值。為什么不直接返回key.hashCode()呢?還要與 (h >>> 16)異或。首先要了解以下知識點:
必備知識點.:^ 運算 >>>運算 &運算。
1. h >>> 16 是什么,有什么用?
h是hashcode。h >>> 16是用來取出h的高16,(>>>是無符號右移) 如下展示:
0000 0100 1011 0011 1101 1111 1110 0001
>>> 16
0000 0000 0000 0000 0000 0100 1011 0011
2. 為什么 h = key.hashCode()) 與 (h >>> 16) 異或
講到這里還要看一個方法indexFor,在jdk1.7中有indexFor(int h, int length)方法。jdk1.8里沒有,但原理沒變。下面看下1.7源碼
1.8中用tab[(n - 1) & hash]代替但原理一樣。
static int indexFor(int h, int length) {
return h & (length-1);
}
這個方法返回值就是數組下標。我們平時用map大多數情況下map里面的數據不是很多。這里與(length-1)相&,
但由於絕大多數情況下length一般都小於2^16即小於65536。所以return h & (length-1);結果始終是h的低16位與(length-1)進行&運算。如下例子(hashcode為四字節)
例如1:為了方便驗證,假設length為8。HashMap的默認初始容量為16
length = 8; (length-1) = 7;轉換二進制為111;
假設一個key的 hashcode = 78897121 轉換二進制:100101100111101111111100001,與(length-1)& 運算如下
0000 0100 1011 0011 1101 1111 1110 0001
&運算
0000 0000 0000 0000 0000 0000 0000 0111
= 0000 0000 0000 0000 0000 0000 0000 0001 (就是十進制1,所以下標為1)
上述運算實質是:001 與 111 & 運算。也就是哈希值的低三位與length與運算。如果讓哈希值的低三位更加隨機,那么&結果就更加隨機,如何讓哈希值的低三位更加隨機,那么就是讓其與高位異或。
補充知識:
當length=8時 下標運算結果取決於哈希值的低三位
當length=16時 下標運算結果取決於哈希值的低四位
當length=32時 下標運算結果取決於哈希值的低五位
當length=2的N次方, 下標運算結果取決於哈希值的低N位。
3. 原因總結
由於和(length-1)運算,length 絕大多數情況小於2的16次方。所以始終是hashcode 的低16位(甚至更低)參與運算。要是高16位也參與運算,會讓得到的下標更加散列。
所以這樣高16位是用不到的,如何讓高16也參與運算呢。所以才有hash(Object key)方法。讓他的hashCode()和自己的高16位^運算。所以(h >>> 16)得到他的高16位與hashCode()進行^運算。
4. 為什么用^而不用&和|
因為&和|都會使得結果偏向0或者1 ,並不是均勻的概念,所以用^。
這就是為什么有hash(Object key)的原因。
————————————————
版權聲明:本文為CSDN博主「楊濤的博客」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_42034205/article/details/90384772