淺談HashMap中的hash算法

淺談HashMap中的hash算法

HashMap是我們常見的一種數據結構,實現Map接口,用來存儲鍵值對,允許null鍵/值、非同步、不保證有序(比如插入的順序)。那HashMap中最核心的部分就是哈希函數,又稱散列函數。也就是說,哈希函數是通過把key的hash值映射到數組中的一個位置來進行訪問。比如:

存在一組哈希值 10,13,7,5,4,20
存在一個長度爲10的數組 arrays
定義一個hash函數 int index = h % arrays.length; 

10 % 10 = 0 那麼 哈希值爲10的對象放在數組索引爲0的位置上;
13 % 10 = 3 那麼 哈希值爲13的對象放在數組索引爲3的位置上;
......
20 % 10 = 0 那麼 哈希值爲13的對象放在數組索引爲0的位置上;

這時候大家看出了一個問題,哈希值爲10的對象和哈希值爲20的對象,放在了一個索引上。發生了碰撞,那麼怎麼解決這樣碰撞呢,有很多種方式,這裏不展開敘述。HashMap中維護了一個鏈表組成的數組。如果衝突的話就添加到鏈表中,下面來看下hashmap中的hash算法,以Java8源碼爲例。

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

其中,key.hashCode()是Key自帶的hashCode()方法,返回一個int類型的散列值。我們大家知道,32位帶符號的int表值範圍從-2147483648到2147483648。這樣只要hash函數鬆散的話,一般是很難發生碰撞的,因爲HashMap的初始容量只有16。但是這樣的散列值我們是不能直接拿來用的。用之前需要對數組的長度取模運算。得到餘數纔是索引值。我們來看下HashMap中怎麼實現的。

int index = hash & (arrays.length-1);

那麼這也就明白了爲什麼HashMap的數組長度是2的整數冪。比如以初始長度爲16爲例,16-1 = 15,15的二進制數位00000000 00000000 00001111。可以看出一個基數二進制最後一位必然位1,當與一個hash值進行與運算時,最後一位可能是0也可能是1。但偶數與一個hash值進行與運算最後一位必然爲0,造成有些位置永遠映射不上值。
但是這時,又出現了一個問題,即使散列函數很鬆散,但只取最後幾位碰撞也會很嚴重。這時候hash算法的價值就體現出來了,
擾動函數
hashCode右移16位,正好是32bit的一半。與自己本身做異或操作(相同爲0,不同爲1)。就是爲了混合哈希值的高位和地位,增加低位的隨機性。並且混合後的值也變相保持了高位的特徵。

HashMap中用到的編碼思想確實很值得我們學習。HashMap在Java1.8後又進行了優化,比如引入紅黑樹的數據結構和擴容的優化等。有機會我們再結合Java1.8聊聊,HashMap get()和put()實現原理,裝載因子,resize()方法還有紅黑樹等。

轉載出:http://ibat.xyz/2017/02/16/淺聊HashMap中的hash算法/

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