jdk1.8中的hashmap做了不少改進:紅黑樹的引入,鏈表尾插,以及底層數組長度保持2的次冪。本文專一於分析2次冪設定的緣由,且聽我慢慢道來……算法
衆所周知,hashmap是數組鏈表結構:hash算法用於將key散列,經計算分散到數組槽中;而兩個key算出了一樣的值,即產生hash衝突時,就須要將槽中的單個節點升級成鏈表。因爲get時須要對鏈表其進行遍歷,鏈表越長檢索效率越差。那麼,計算出的key值落點越平均,hash衝突的可能性越小。數組
key值落點的計算方式爲,key的hash值
與數組長度
做取餘
操做,記做key.hascode % array.length
。從數學角度考慮,保持array.length
爲質數會使得計算結果更均衡,hashTable就是這麼作的(數組初始值11)。但 hashmap 中 array.length 恰恰選擇了2的次冪,是個合數……何故?徹底出於性能考慮!
先給出結論——當 array.ength長度是2的次冪時,key.hashcode % array.length
等於key.hashcode & (array.length - 1)
。下面重點看下這個結論是怎麼得出來的。性能
舉個例子:
假如 array.length = 2^4 = 16,二進制10000。這個數減去1的結果是1111,也就是array.length -1 = 1111。
(下面這段中的數字都是二進制)
再假設一個key的值爲10011011001(很隨意寫的一個數),與1111作 & 操做,獲得的結果是1001(高位部分1001101
都捨去了)。而1001必然是一個小於10000的數,對於一個小於10000的數而言,1001 % 10000獲得的就是1001本身。
那麼剛剛捨棄的高位部分1001101 0000(後面補上了四個0000)就必定能被10000整除嗎?答案是確定的:由於10011010000能夠拆成10000000000+10000000+1000000+10000,這幾個數都能經過10000的n次左移獲得,也就至關於這幾個數都能被10000整除。那他們的和,也就是10011010000,必定也能夠被10000整除。
所以,最終結論就是:10011011001 & ( 10000 - 1 ) = 10011011001 & 1111 = 1001 = 10011011001 % 10000
spa
放張簡圖再嘮叨一遍以示總結,加深下印象:3d
再強調一次:當 array.ength長度是2的次冪時,key.hashcode % array.length
等於key.hashcode & (array.length - 1)
code
好,若是你讀懂了例子部分,相信你已經基本明白這個結論是站得住腳的(雖然不是純數學型的講解)。那麼hashmap的做者Doug Lea大神,爲何如此執着於用&
操做替換%
操做呢?
由於對於二進制生物計算機來講,& 的效率要高於 %!(與、或、非均可看做二進制基本操做,同或、異或次之,+ - * ÷ % 等都基於前面的)blog
這還不算完,好處不止這一處。
當hashmap須要擴容,從新計算鏈表元素的hashcode,以進行元素的從新定位時,依然能從「 數組2次冪 」的這個設定中借力!索引
hashmap數組擴容時,新數組length = 原數組length * 2,沿用前面的例子(array.length = 2^4 = 16,二進制10000),array.length 乘以 2 ,即二進制左移一位,由 10000 變成 100000。此時須要從新計算數組槽中的元素位置,若是槽中是鏈表,鏈表中每一個元素都須要從新計算位置(這裏不考慮紅黑樹)。ip
計算的公式不變,key.hashcode & (array.length - 1)
,因爲數組的翻倍(10000->100000),致使 array.length - 1 發生了改變(1111->11111)。此時,擴容前本來被捨棄的高位部分的最後1位,也將參與計算。get
在擴容這個歷史的拐點,這一位就顯得很特別:若是這個位置是0,餘數計算的結果將保持不變,意味着擴容後此元素還在這個槽中(槽編號沒發生改變);若是這個位置是1,餘數計算結果就變成了原槽索引 + 原array.length
。
也就是說,hashmap擴容的元素遷移過程當中,因爲數組大小是2次冪的巧妙設定,使得只要檢查 「 特殊位 」 就能肯定該元素的最終定位。
給出一個較完整的擴容示意圖進行說明:
紅綠黃三個元素,由各自的hashcode取餘後都淤積在數組槽13,組成鏈表形式
紅、綠二星所表示的元素的hashcode「 特殊位 」爲0,取餘依然定位在槽13;而黃星表示的元素,hashcode「 特殊位 」爲1,取餘後結果 = 原槽索引 + 原數組大小 = 13 + 16 = 29。(這個結果也和圖中黃星的hashcode二進制低位值11101一致)
對hashmap而言,數組長度始終保持2次冪有兩點好處:
性能,性能,仍是性能……