为什么hashmap的容量必须是2的n次幂
?
要明白為什么是2的n次冪,這要從hashmap的hash方式說起,hashmap的容量期望就是用來均勻散列存放map中的元素。hashmap根據(jù)hash值把元素放到hashmap內(nèi)部數(shù)組的一個(gè)位置上。
1、為什么hashmap的容量必須是2的n次冪??
我們不妨先看看hashCode的原理,以String為列,獲取hashCode的方法源碼
public int hashCode() {int h = hash;if (h == 0 && value.length > 0) {char val[] = value;for (int i = 0; i < value.length; i++) {h = 31 * h + val[i];}hash = h;}return h;}可以看出任何對(duì)象的hashCode方法返回一個(gè)對(duì)應(yīng)的int類型的結(jié)果,所以hashCode的取值范圍是-2^31 ~ (2^31-1),即-2147483648~2147483647。
(值得注意的是:兩個(gè)對(duì)象equals相等,hashCode一定相等,hashCode相等,equals不一定相等,從上段代碼hashCode源碼中可以得出這個(gè)結(jié)論,會(huì)存在不同的數(shù),計(jì)算出相同的hashCode)
當(dāng)要存入map中的條目數(shù)為n(0 < n <= 1073741824)的時(shí)候,這n個(gè)數(shù)要散列分布到map數(shù)組中,那么map數(shù)組的長(zhǎng)度為多少,才能使得元素在map數(shù)組最為呈現(xiàn)散列均勻分布,又不浪費(fèi)空間呢?那么就是要求每個(gè)數(shù)通過某種算法,填入到數(shù)組表中的概率是相等的。這種算法最容易想到的就是取模運(yùn)算。
我們聯(lián)想下通過運(yùn)算能不能達(dá)到類似效果呢,因?yàn)橛?jì)算機(jī)本身就是通過位運(yùn)算完成所有計(jì)算的,通過測(cè)試發(fā)現(xiàn)位運(yùn)算比模運(yùn)算快約27倍。怎么個(gè)位運(yùn)算,使得n個(gè)元素能在有指定數(shù)組長(zhǎng)度的情況下,得到散列分布索引值呢?
參考hashMap獲取索引的算法源碼
public static int indexFor(int h, int length) {return h & (length-1);}indexFor中的h是hashCode通過變換之后的值。怎么變換呢?為什么要變換呢?hashCode結(jié)果是一個(gè)32位的二進(jìn)制數(shù),如果直接用如此長(zhǎng)的二進(jìn)制數(shù)和目標(biāo)length-1直接進(jìn)行與運(yùn)算結(jié)果為怎么樣呢?答案是高位(也就是左邊的位)會(huì)大量丟失。看看下面例子
如果兩個(gè)數(shù)的hashCode分別是
11001110 11001101 11011101 00111110
11001110 11001111?11011101 00111110
只有其中第15位不一樣(任何高位不一樣都可以,假如我們以16位為劃分,任何兩個(gè)高16位不一樣,低16位一樣的數(shù)),這兩個(gè)hashCode與length-1做與運(yùn)算,當(dāng)length<2的16次方時(shí)(1<<16 =?65536)得到兩個(gè)hashCode &?length-1 的結(jié)果一樣,這樣的兩個(gè)數(shù),卻產(chǎn)生了相同的hash結(jié)果,于是hashMap想到了一種處理方式:將hashCode的高16位于低16位進(jìn)行異或運(yùn)算,其實(shí)吧用普通話說就是把高16位的特征放到低16位中,讓低低16位同時(shí)擁有了高16的特征。這樣,在與length-1做與運(yùn)算時(shí)結(jié)果就不一樣了。其計(jì)算過程如下:假如有hashCode a=11001110 11001111 11011101 00111110
int a = 11001110 11001111 11011101 00111110a >>> 16 = 00000000 00000000 11001110 11001111a ^ (a >>> 16)11001110 11001111 11011101 00111110 00000000 00000000 11001110 11001111 = 11001110 11001111 00010011 11110001用java代碼表達(dá)?
static final int hash(Object key) {int h;return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);}hashCode結(jié)果是32位int 結(jié)果,將32位的結(jié)果做變換,可以從下面例子來研究下
int a = 11001110 11001111 11011101 00111110a >>> 16 = 00000000 00000000 11001110 11001111a ^ (a >>> 16)11001110 11001111 11011101 00111110 00000000 00000000 11001110 11001111 = 11001110 11001111 00010011 11110001在算法上可以看出,32位hashcode中保持高16位不變,高16與低16異或結(jié)果作為新的低16位。然后用hash得到的結(jié)果傳入方法indexFor獲取到hashMap的索引。
hash與n-1 做與運(yùn)算,這樣既在容量遠(yuǎn)小于1<<16(往往hashmap的容量遠(yuǎn)小于65535) 的情況下,hash & (n-1),在計(jì)算中只有低位((n-1)對(duì)應(yīng)的二進(jìn)制數(shù)相同的位數(shù))參與&運(yùn)算,計(jì)算效率高,同時(shí)也保證的hash的高16位參與了索引運(yùn)算。這樣得到的索引能呈較為理想的散列分布,在將條目放入hashMap中時(shí),最大限度避免hash碰撞。
設(shè)hashMap容量為16,那么我們看下計(jì)算出來的索引值
11001110 11001111 00010011 11110001 & 00000000 00000000 00000000 00001111 = 00000000 00000000 00000000 00000001索引=1,將該條目存入hash表標(biāo)號(hào)為1的位置處
回到標(biāo)題中的問題:為什么容量一定要是2的n次冪,只有當(dāng)length=2的n次冪的時(shí)候,length-1的二進(jìn)制表達(dá) 全部為1(15的二進(jìn)制1111,31的二進(jìn)制位11111),只有當(dāng)length-1的全部位都1時(shí),h & (length-1)的結(jié)果才能均勻散列在數(shù)組中。這時(shí),取模和與運(yùn)算兩種運(yùn)算才是等價(jià)不等效的。至此就解釋完了為什么hashMap的容量必須是2的整數(shù)次冪。
2、hashmap怎么去計(jì)算容量呢?怎么確保程序員傳入的值是2的n次冪呢,答案是不能保證。那就只能通過內(nèi)部算法hashMa自己來實(shí)現(xiàn)保證。
先不考慮擴(kuò)容的情況下,通過傳入我們自定義的容量值來構(gòu)造HashMap實(shí)例
public HashMap(int initialCapacity) {// DEFAULT_LOAD_FACTOR = 0.75this(initialCapacity, DEFAULT_LOAD_FACTOR);}怎么保證傳入的容量一定2的整數(shù)次冪,可以從源碼來看,通過下面方法等到保證,結(jié)果一定是2的整數(shù)次冪,結(jié)果是大于等于initialCapacity的最小2的整數(shù)次冪。
n |= n >>> 1, ?符號(hào) | 為或運(yùn)算,n 與 n>>>1 進(jìn)行或運(yùn)算,然后賦值給n
static final int tableSizeFor(int cap) {int n = cap - 1;n |= n >>> 1;n |= n >>> 2;n |= n >>> 4;n |= n >>> 8;n |= n >>> 16;return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;}當(dāng)不指定Hashmap容量是,初始化默認(rèn)容量為16,當(dāng)往HashMap中put時(shí),會(huì)檢查當(dāng)前hash表中的條目數(shù)是否大于容量的0.75倍,如果滿足大于容量的0.75,hashMap調(diào)用resize方法,新容量 = 原來容量 x 2,在resize時(shí)將原來的容量翻倍,然后把之前所有條目復(fù)制一遍放入擴(kuò)容之后的hash表中,這是非常好性能的操作,所以在已知容量數(shù)量的情況下避免擴(kuò)容的情況發(fā)生。
?
總結(jié)
以上是生活随笔為你收集整理的为什么hashmap的容量必须是2的n次幂的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 桌面计算机图标管理打不开怎么回事,电脑桌
- 下一篇: 第二十九章 狼心狗肺