日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

为什么hashmap的容量必须是2的n次幂

發布時間:2024/3/13 编程问答 35 豆豆
生活随笔 收集整理的這篇文章主要介紹了 为什么hashmap的容量必须是2的n次幂 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?

要明白為什么是2的n次冪,這要從hashmap的hash方式說起,hashmap的容量期望就是用來均勻散列存放map中的元素。hashmap根據hash值把元素放到hashmap內部數組的一個位置上。

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;}

可以看出任何對象的hashCode方法返回一個對應的int類型的結果,所以hashCode的取值范圍是-2^31 ~ (2^31-1),即-2147483648~2147483647。

(值得注意的是:兩個對象equals相等,hashCode一定相等,hashCode相等,equals不一定相等,從上段代碼hashCode源碼中可以得出這個結論,會存在不同的數,計算出相同的hashCode)

當要存入map中的條目數為n(0 < n <= 1073741824)的時候,這n個數要散列分布到map數組中,那么map數組的長度為多少,才能使得元素在map數組最為呈現散列均勻分布,又不浪費空間呢?那么就是要求每個數通過某種算法,填入到數組表中的概率是相等的。這種算法最容易想到的就是取模運算。

我們聯想下通過運算能不能達到類似效果呢,因為計算機本身就是通過位運算完成所有計算的,通過測試發現位運算比模運算快約27倍。怎么個位運算,使得n個元素能在有指定數組長度的情況下,得到散列分布索引值呢?

參考hashMap獲取索引的算法源碼

public static int indexFor(int h, int length) {return h & (length-1);}

indexFor中的h是hashCode通過變換之后的值。怎么變換呢?為什么要變換呢?hashCode結果是一個32位的二進制數,如果直接用如此長的二進制數和目標length-1直接進行與運算結果為怎么樣呢?答案是高位(也就是左邊的位)會大量丟失。看看下面例子

如果兩個數的hashCode分別是

11001110 11001101 11011101 00111110

11001110 11001111?11011101 00111110

只有其中第15位不一樣(任何高位不一樣都可以,假如我們以16位為劃分,任何兩個高16位不一樣,低16位一樣的數),這兩個hashCode與length-1做與運算,當length<2的16次方時(1<<16 =?65536)得到兩個hashCode &?length-1 的結果一樣,這樣的兩個數,卻產生了相同的hash結果,于是hashMap想到了一種處理方式:將hashCode的高16位于低16位進行異或運算,其實吧用普通話說就是把高16位的特征放到低16位中,讓低低16位同時擁有了高16的特征。這樣,在與length-1做與運算時結果就不一樣了。其計算過程如下:假如有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代碼表達?

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

hashCode結果是32位int 結果,將32位的結果做變換,可以從下面例子來研究下

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異或結果作為新的低16位。然后用hash得到的結果傳入方法indexFor獲取到hashMap的索引。

hash與n-1 做與運算,這樣既在容量遠小于1<<16(往往hashmap的容量遠小于65535) 的情況下,hash & (n-1),在計算中只有低位((n-1)對應的二進制數相同的位數)參與&運算,計算效率高,同時也保證的hash的高16位參與了索引運算。這樣得到的索引能呈較為理想的散列分布,在將條目放入hashMap中時,最大限度避免hash碰撞。

設hashMap容量為16,那么我們看下計算出來的索引值

11001110 11001111 00010011 11110001 & 00000000 00000000 00000000 00001111 = 00000000 00000000 00000000 00000001

索引=1,將該條目存入hash表標號為1的位置處

回到標題中的問題:為什么容量一定要是2的n次冪,只有當length=2的n次冪的時候,length-1的二進制表達 全部為1(15的二進制1111,31的二進制位11111),只有當length-1的全部位都1時,h & (length-1)的結果才能均勻散列在數組中。這時,取模和與運算兩種運算才是等價不等效的。至此就解釋完了為什么hashMap的容量必須是2的整數次冪。

2、hashmap怎么去計算容量呢?怎么確保程序員傳入的值是2的n次冪呢,答案是不能保證。那就只能通過內部算法hashMa自己來實現保證。

先不考慮擴容的情況下,通過傳入我們自定義的容量值來構造HashMap實例

public HashMap(int initialCapacity) {// DEFAULT_LOAD_FACTOR = 0.75this(initialCapacity, DEFAULT_LOAD_FACTOR);}

怎么保證傳入的容量一定2的整數次冪,可以從源碼來看,通過下面方法等到保證,結果一定是2的整數次冪,結果是大于等于initialCapacity的最小2的整數次冪。

n |= n >>> 1, ?符號 | 為或運算,n 與 n>>>1 進行或運算,然后賦值給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;}

當不指定Hashmap容量是,初始化默認容量為16,當往HashMap中put時,會檢查當前hash表中的條目數是否大于容量的0.75倍,如果滿足大于容量的0.75,hashMap調用resize方法,新容量 = 原來容量 x 2,在resize時將原來的容量翻倍,然后把之前所有條目復制一遍放入擴容之后的hash表中,這是非常好性能的操作,所以在已知容量數量的情況下避免擴容的情況發生。


?

總結

以上是生活随笔為你收集整理的为什么hashmap的容量必须是2的n次幂的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。