HashMap原理解析

1. Hash函數算法

       Hash,通常翻譯作「散列」,也有直接音譯爲「哈希」的,就是把任意長度的輸入(關鍵字),經過散列算法,變換成固定長度的輸出,該輸出就是散列值。 這種轉換是一種壓縮映射(輸入的長度小於輸出的長度)。常見的Hash函數有如下幾個:除留餘數法、直接尋址法、數字分析法、平方取中法、分段摺疊法、隨機數法。數組

  1.1 除留餘數法 : hash(k) = k % a. [其中,a爲正整數]安全

                    直接以餘數做爲散列值。【HashMap中,a爲HashMap的表長函數

  1.2 直接尋址法:hash(k) = a * k + c .[其中,a,c爲常數 & a <> 0]性能

      直接以關鍵字的某個線性函數做爲散列值。spa

  1.3 數字分析法 : 線程

     提取關鍵字中取值比較均勻(重複機率比較小)的數字做爲散列值。例如:同一個班級的學生的生日,年份的重複率比較高,衝突的機率大,因此取值的時候就儘可能避開年份。翻譯

  1.4 平方取中法:hash(k) = (k^2) / (10^a) % (10^b)  [其中,a,b爲正整數 & a > b]code

      取關鍵字平方後的中間幾位爲哈希地址。對象

  1.5 分段摺疊法 :

     將關鍵字分紅長度相等的幾段(最後一段能夠短一些),將幾部分相加,捨棄最高位的結果做爲散列值。

  1.6 隨機數法 :

    採用一個隨機數做爲散列值。(計算機中沒有真正的隨機數,所謂的隨機數都是經過某種算法獲取。既然有算法,就不可能真正的隨機)

 

2.Hash碰撞

        對不一樣的關鍵字可能獲得同一散列地址,即key1≠key2,而hash(key1)=hash(key2),這就是hash碰撞衡量一個哈希函數的好壞的重要指標就是發生碰撞的機率以及發生碰撞的解決方案(任何哈希函數基本都沒法完全避免碰撞)。常見的解決碰撞的方法有如下幾種:鏈地址法、開放地址法、再哈希法、創建公共溢出區。

  2.1 鏈地址法:將哈希表的每一個單元做爲鏈表的頭結點,全部散列值(即哈希地址)相同的元素添加到一個鏈表裏。

  2.2 開放地址法:一旦發生Hash碰撞,就去尋找下一個空的哈希地址。

  2.3 再哈希法:當Hash碰撞時,就再次經過hash函數計算出新的哈希地址,直到碰撞再也不發生。

  2.4 創建公共溢出區:將哈希表分爲基本表和溢出表兩部分,放生Hash碰撞的元素都存入溢出表中。

 

3. HashMap

  3.1 HashMap由數組鏈表組成(數組是主體,數組中存放的對象是鏈表對象),即採用鏈地址法解決Hash碰撞採用除留餘數法肯定數組下標)。對於哈希地址相同的key-value對象,依次被添加到鏈表的尾端。

  3.2 HashMap中,鏈表對象(Entry<K,V>)的存儲地址(即:索引)是經過hash(int h)和indexFor(int h, int length)聯合獲取的。

/**
 * 該方法是爲了獲取高質量的hashCode,即:減小碰撞的機率,儘可能作到任何一位的變化都能對最終獲得的結果產生影響。
 * 算法思想:把高位的特徵和低位的特徵組合起來
 */

//Java 7
static int hash(int h) {
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);

}

//Java 8
static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
/**
 * 該方法是爲了獲取數組的下標。
 * 算法思想:將hashcode對數組的長度取模
 */
static int indexFor(int h, int length) {
    //等價於: h % length
    //由於數組的長度恆等於2^n,因此能夠經過位運算符計算(直接對內存操做,不用轉換成十進制,效率高)
    //位運算還能夠很好的解決負數的問題(負數的模運算結果仍爲負數,可是位運算返回的結果爲正數).
    return h & (length-1);
}

  3.3 Java 7中使用單向鏈表存儲衝突元素,Java 8中使用平衡樹來替代鏈表存儲衝突的元素。在最壞的狀況下,Java7將HashMap的get方法的性能爲 O(n),Java 8的性能爲O(logn) 。若是惡意程序知道咱們用的是Hash算法,則在單向鏈表狀況下,它可以發送大量請求致使哈希碰撞,而後不停訪問這些key致使HashMap忙於進行線性查找,最終陷入癱瘓,即造成了拒絕服務攻擊(DoS)。

   3.4 HashMap中,key、value均可覺得null。

   3.5 HashMap的數組是有長度的,Java中規定這個長度只能是2的倍數(即:size = 2^n),初始值爲16。每次擴容爲原來的二倍。

   3.6 HashMap 的實現不是線程同步的,這意味着它不是線程安全的。

   3.7 加載因子:是哈希表在其容量自動增長以前能夠達到多滿的一種尺度,默認值爲0.75。【當哈希表的數據量 >=(容器*加載因子)時,哈希表將進行擴容】

 

4. 參考資料

  4.1 百度百科 : 《Hash(散列函數)》

  4.2 Hollis : 《全網把 Map 中的 hash() 分析的最透徹的文章,別無二家》

  4.3 JDK源碼

相關文章
相關標籤/搜索