HashMap與HashTable的哈希算法——JDK1.9源碼閱讀總結

下面是HashTable源碼中的put方法:git

注意上面註釋標註的地方:github

HashTable對於元素在哈希表中的座標算法是:算法

  1. 將對象自身的哈希值key.hashCode()變爲正數:hash & 0x7FFFFFFF
  2. 將上面獲得的哈希值對錶長取餘,映射到哈希表中去。

HashMap中哈希算法比HashTable中的稍微複雜一點。整體能夠分爲兩步:markdown

1、從新計算key自己的哈希值

上面代碼中,首先是一個三目運算符,判斷key是否是等於null,等於null,則返回0做爲哈希值。不然,運算(h=key.hashCode()) ^ (h >>> 16),將key的哈希值的高位與低位異或的結果做爲低位,改成不變。優化

‘>>>’是無符號右移操做,高位補0.atom

爲何要這麼作呢?下面這一點講了事後咱們就明白了spa

2、哈希座標的計算

一樣以put方法爲例.net

從最後一行咱們能夠看出,HashMap的哈希座標計算方法是: (n - 1) & hash,其中hash就是咱們第一點講的改進哈希碼。對象

HashMap爲何要使用改進的hash碼?

舉例分析以下,假設key的原始哈希值是’1111 1111 1111 1111 1111 0000 1110 1010’blog

HashKey
(ps:圖片來自:https://blog.csdn.net/john_520/article/details/57415084)

咱們注意到,在上面這種哈希表長度較小的狀況下,哈希碼只有低4位與表的長度進行了關聯性計算。這會形成哈希碼的不充分使用,從而更容易引發哈希衝突。爲了充分利用哈希碼的高位,HashMap經過(h=key.hashCode()) ^ (h >>> 16)運算,將高位與低位異或,使得即便在表長較小的狀況下,高位也能參與計算,使得衝突的機率減少了。

HashMap爲何要使用 (n - 1) & hash ,而不是HashTable中求模的方式來計算哈希座標呢?

我在Stack Overflow上找到了一個解答:

意思是說:「規範的解決方法是將哈希值與表長取模,而這個方式((n - 1) & hash )武漢英語學校充分利用了HashMap的表長是2的整數次冪的事實,使用效率較高的位與運算(取模的高度優化)來替代昂貴的取模運算。」
實際上這兩種方式的實質是同樣的,它只是利用了這樣一個事實:在n是2的冪的狀況下,(n - 1) & hash 等同於 hash%h。

在n是2的冪的狀況下,爲何 (n - 1) & hash等同於 hash%h?

咱們以10爲例,演示以下:

能夠看到,兩種方式的計算結果是相同的(這不是巧合),這其實是一個數學規律。

HashMap是怎麼使得表長始終爲2的整數次冪的?

在源碼中有這樣一個方法:

這個方法的做用如註釋所說,是求大於cap的最小2的整數次冪。在用戶指定的初始容量不是2的冪時,HashMap會調用該方法將其變得符合要求。此後,每次擴容時是這樣的:

直接使用oldCap<<1來將容量擴大爲原來的2倍,即乘以21

  1. 對數學規律的恰當應用能夠優化代碼的運行效率
  2. 位運算在JDK中運用的十分普遍,如上面講解的使用位「與」運算替代求模。這種替代的內在緣由是位運算比數學運算快不少。優化都在細節處。

我對JDK源碼的閱讀和中文註釋都已經同步到Github,歡迎英語閱讀困難戶前往查看:)
連接是:https://github.com/Dodozhou/JDK,喜歡的話別忘了star哦。

相關文章
相關標籤/搜索