Java中HashCode算法詳解
Java中的集合,比如HashMap/HashSet/HashTable在實現上都用到了hashCode算法,用來計算元素在數組中的位置。hashCode是Object類中的一個方法,所以,所有的Java類都有這個方法,只是一些類對這個方法進行了覆寫,下面以String類的實現為例進行說明:
public int hashCode() {
????int h =hash;
? ? if (h ==0 &&value.length >0) {
????????char val[] =value;
? ? ? ? for (int i =0; i <?value.length; i++) {
????????h =31 * h + val[i];
? ? ? ? }
????????hash = h;
? ? }
????return h;
}
其實這個算法的實現很簡單,以“hangzhou”這個字符串為例,計算過程如下:
第一步:int ‘h’
第二步:31 * (第一步結果) + int ‘a’
第三步:31 * (第二部結果) + int ‘n’
第四步:31 * (第三步結果) + int ‘g’
第五步:31 * (第四步結果) + int ‘z’
第六步:31 * (第五步結果) + int ‘h’
第七步:31 * (第六步結果) + int ‘o’
第八步: 31 * (第七步結果) + int ‘u’
可以得到“hangzhou”的hashcode為4740586。
為什么HashMap中的&位必須位奇數(length-1)
從key映射到HashMap數組的對應位置需要一個Hash函數:
index = Hash("hangzhou")
如何實現一個盡量分布均勻的hash函數呢?我們使用key的hashcode做某種運算:
index = hashCode("hangzhou") & (Length - 1) 其中,Length為HashMap的長度,下面來演示整個過程:
1、“hangzhou”的hashcode為4740586,二進制表示為100 1000 0101 0101 1110 1010
2、假定HashMap的長度為默認的16,則Length - 1為15,也就是二進制的1111
3、把以上兩個結果做與運算,得到的結果為1010,也就是index為10
可以說,Hash算法最終得到的index結果完全取決于hashCode的最后幾位。
假設,HashMap的長度為10,則Length - 1為9,也就是二進制的1001,通過Hash算法得到的最終index為8,當只有一個元素的時候這沒問題。但是我們再來試一個hashCode:100 1000 0101 0101 1110 1110時,通過Hash算法得到的最終的index也是8,另外還有100 1000 0101 0101 1110 1000得到的index也是8。也就是說,即使我們把倒數第二、三位的0、1變換,得到的index仍舊是8,說明有些index結果出現的幾率變大!!而有些index結果永遠不會出現,比如二進制0000.
這樣,顯然不符合Hash算法均勻分布的要求。
反觀,長度16或其他2的冪次方,Length - 1的值的二進制所有的位均為1,這種情況下,Index的結果等于hashCode的最后幾位。只要輸入的hashCode本身符合均勻分布,Hash算法的結果就是均勻的。
一句話,HashMap的長度為2的冪次方的原因是為了減少Hash碰撞,盡量使Hash算法的結果均勻分布。