HashMap的默認長度爲什麼是16??

本文轉載至:http://blog.csdn.net/zjcjavahttps://blog.csdn.net/zjcjava/article/details/78495416

背景

哈希算法在Java中是經常用的的一個算法,也是一些常用數據結構中必用的一個算法,它爲上層的複雜數據結構提供了基礎支撐。

哈希算法的實現有很多種,除了這裏講的map中的hashcode算法,還有其他哈希算法:

1.直接定址法 
2.數字分析法 
3.摺疊法 
4.平方取中法 
5.減去法 
6.字符串數值哈希法 
7.旋轉法

更多算法請參考另一篇作者的文章: 
hash算法原理詳解

HashMap中的HashCode算法詳解

1,哈希算法在HashMap類中的應用 
java中的集合,比如HashMap/Hashtable/HashSet等,在實現時,都用到了哈希算法。當我們向容器中添加元素時,我們有必要知道 
這個元素是否已經存在。 
從實現上來說,java是藉助hashcode()方法和equals()方法來實現判斷元素是否已經存在的。當我們向HashMap中插入元素A時,首先, 
調用hashcode()方法,判斷元素A在容器中是否已經存在。如果A元素的hashcode值在HashMap中不存在,則直接插入。否則,接着調用 
equals()方法,判斷A元素在容器中是否已經存在。hashcode()的時間複雜度爲O(1),equals()方法的時間複雜度爲O(m),整體的時間複雜度 
就是:O(1) + O(m)。其中,m是桶的深度。桶的深度是一個什麼概念呢,桶的深度是指具有相同hashcode值得元素的個數,也就是發生哈希 
碰撞的元素的個數。 
一個好的哈希算法應該儘量減少哈希碰撞的次數。

HashCode是Object中本身就有的方法,但是沒有具體實現。而各個不同的數據類型又繼承實現了各自的具體HashCode算法,這裏只以String類型的HashCode爲例。

public int hashCode() {  
int h = hash;  
if (h == 0) {  
    int off = offset;  
    char val[] = value;  
    int len = count;  

        for (int i = 0; i < len; i++) {  
            h = 31*h + val[off++];  
        }  
        hash = h;  
    }  
    return h;  
}  

源代碼寫的比較簡潔,閱讀起來也不是太方便,下面我詳細解讀一下: 
// String類的hashcode值(哈希值)是如何計算得到的?具體實現?爲了方便閱讀,我們來進行分步說明

static void hashcodeTest(){  

    String str = "yangcq";  

    // 第一步 = (int)'y'  
    // 第二步 = (31 * (int)'y') + (int)'a'  
    // 第三步 = 31 * ((31 * (int)'y') + (int)'a') + (int)'n'  
    // 第四步 = 31 * (31 * ((31 * (int)'y') + (int)'a') + (int)'n') + (int)'g'  
    // 第五步 = 31 * (31 * (31 * ((31 * (int)'y') + (int)'a') + (int)'n') + (int)'g') + (int)'c'  
    // 第六步 = 31 * (31 * (31 * (31 * ((31 * (int)'y') + (int)'a') + (int)'n') + (int)'g') + (int)'c') + (int)'q'  

    // 上面的過程,也可以用下面的方式表示  

    // 第一步 = (int)'y'  
    // 第二步 = 31 * (第一步的計算結果) + (int)'a'  
    // 第三步 = 31 * (第二步的計算結果) + (int)'n'  
    // 第四步 = 31 * (第三步的計算結果) + (int)'g'  
    // 第五步 = 31 * (第四步的計算結果) + (int)'c'  
    // 第六步 = 31 * (第五步的計算結果) + (int)'q'  

    int hashcode = 31 * (31 * (31 * (31 * ((31 * (int)'y') + (int)'a') + (int)'n') + (int)'g') + (int)'c') + (int)'q';  
    System.out.println("yangcq的hashcode = " + hashcode);        // yangcq的hashcode = -737879313  
    System.out.println("yangcq的hashcode = " + str.hashCode());  // yangcq的hashcode = -737879313  

}  

爲什麼HashMap中的&位必須爲奇數(Length - 1)

從Key映射到HashMap數組的對應位置,會用到一個Hash函數:

index = Hash(“apple”)

如何實現一個儘量均勻分佈的Hash函數呢?我們通過利用Key的HashCode值來做某種運算。 
如何進行位運算呢?有如下的公式(Length是HashMap的長度):

index = HashCode(Key) & (Length - 1)

下面我們以“book”的Key來演示整個過程:

1.計算book的hashcode,結果爲十進制的3029737,二進制的101110001110101110 1001。

2.假定HashMap長度是默認的16,計算Length-1的結果爲十進制的15,二進制的1111。

3.把以上兩個結果做與運算,101110001110101110 1001 & 1111 = 1001,十進制是9,所以 index=9。

可以說,Hash算法最終得到的index結果,完全取決於Key的Hashcode值的最後幾位。

假設HashMap的長度是10,重複剛纔的運算步驟: 
這裏寫圖片描述

單獨看這個結果,表面上並沒有問題。我們再來嘗試一個新的HashCode 101110001110101110 1011 : 
這裏寫圖片描述 
讓我們再換一個HashCode 101110001110101110 1111 試試 : 
這裏寫圖片描述

是的,雖然HashCode的倒數第二第三位從0變成了1,但是運算的結果都是1001。也就是說,當HashMap長度爲10的時候,有些index結果的出現機率會更大,而有些index結果永遠不會出現(比如0111)!

這樣,顯然不符合Hash算法均勻分佈的原則。

反觀長度16或者其他2的冪,Length-1的值是所有二進制位全爲1,這種情況下,index的結果等同於HashCode後幾位的值。只要輸入的HashCode本身分佈均勻,Hash算法的結果就是均勻的。

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