JDK8 對 hash算法和尋址算法作了哪些優化?

點擊藍色「程序員大帝 」關注我喲java

加個「星標」,及時閱讀最新技術文章node

每日雞湯,好喝


正文

接着上篇文章《HashMap加載因子爲何是0.75?轉化紅黑樹閾值爲8?》,我們繼續聊聊 HashMap 這個重要的數據結構。雖然它很簡單,可是每一次讀源碼,我都有不一樣的體會,固然惟一不變的是對 Doug Lea 大神的崇拜。
nginx


在 JDK8 以後,對 HashMap 進行了重寫,最顯而易見的固然是引入了紅黑樹。由此而來,對它的哈希算法和尋址算法也作了必定的優化。程序員

正文

尋址算法面試

在插入和查找數據的時候,咱們會根據 key 獲得它對應的 hash 值,而後再根據這個 hash 值進行一系列計算,獲得元素在數組的下標位置,這個計算過程就是就是尋址算法。算法


 final Node<K,V> getNode(int hash, Object key) {
Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        if ((tab = table) != null && (n = tab.length) > 0 &&            (first = tab[(n - 1) & hash]) != null) {            if (first.hash == hash && // always check first node                ((k = first.key) == key || (key != null && key.equals(k)))) return first;
if ((e = first.next) != null) {
                if (first instanceof TreeNode) return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                do {                    if (e.hash == hash &&                        ((k = e.key) == key || (key != null && key.equals(k))))                        return e;                } while ((e = e.next) != null);            } }        return null; }

其中最關鍵的是下面這一行,它展現瞭如何經過計算好的 hash 值來獲得對應的哈希槽的位置:數組

first = tab[(n - 1) & hash])


你們第一個想法確定是經過模運算來計算,所以引出了下面的問題
bash

HashMap 中尋址算法爲何使用&(與運算),代替模運算?微信

咱們知道了一個 key 的 hash 值,用這個hash值跟數組長度取模,就能夠獲得下標位置,其中 n 是數組的長度數據結構

(n - 1) & hash


若是使用與運算,其實該算法的結果和模運算的結果是相同的。


可是,對於現代的處理器來講,除法和求餘數(模運算)是最慢的動做。


根據數學公式:


a % b = (b-1) & a

 

當 b 是 2 的指數時,等式成立。你們應該記起來,HashMap 默認的長度必定是 2 的指數冪,因此這一個等式針對 HashMap 是永遠成立的。經過與運算,提升了運算的效率。

哈希算法

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

看完源代碼,第一次確定會被其中的異或運算和右移運算搞蒙,爲何要異或呢?爲何要移位?並且移位 16?


咱們分析一下:


首先,假設有一種狀況,若是數組長度 n=16,那麼根據尋址算法,也就是哈希值 和 15 這個數進行與運算


對象 A 的 hashCode 爲 1000 0100 0111 0001 0000 0111 1000 0000

n-1=15                       0000 0000 0000 0000 0000 0000 0000 1111        



對象 B 的 hashCode 爲 0111 0111 0011 1000 1010 0001 0100 0000

n-1=15                       0000 0000 0000 0000 0000 0000 0000 1111        


咱們會發現 A、B 和 15 這個數進行與運後,得出來的結果都是 0, 這樣的散列結果太讓人失望了。很明顯不是一個好的散列算法。


可是若是咱們將 hashCode 值右移 16 位,而後再進行異或運算(若是兩個數不一樣,結果爲1,相同爲0),這樣的話,就能避免咱們上面的狀況的發生。



對象A hashCode:               1000 0100 0111 0001 0000 0111 1000 0000


對象A hashCode右移16位:  0000 0000 0000 0000 1000 0100 0111 0001


運算                          1000 0100 0111 0001 1000 0011 1111 0001


n-1=15                                0000 0000 0000 0000 0000 0000 0000 1111  


與運算:                              0000 0000 0000 0000 0000 0000 0000 0001



對象B hashCode:               0111 0111 0011 1000 1010 0001 0100 0000


對象B hashCode右移16位: 0000 0000 0000 0000 0111 0111 0011 1000


異或運算:                          0111 0111 0011 1000 1101 0110 0100 1000


n-1=15                               0000 0000 0000 0000 0000 0000 0000 1111   


與運算:                             0000 0000 0000 0000 0000 0000 0000 1000



經過結果能夠看出來,這樣異或運算獲得結果,再和 n-1 與運算,獲得結果不一樣,避免了 hash 衝突。

文末福利

找工做的小夥伴能夠後臺聯繫我,拉你進秋招/內推/面試羣,我也給你們整理了各大公司的內推通道、簡歷模板還有歷年的筆試題,你們要好好準備哦。


還能夠幫助你們免費修改簡歷、模擬面試哦~可能下期視頻的主角就是你哦~


我是無忌,Stay Tuned!


本文分享自微信公衆號 - 程序員大帝(kingcoding)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。

相關文章
相關標籤/搜索