看過HashMap源碼的人可能都用印象,就是hashMap的哈希表長度可以由自己指定也可以不指定使用默認長度,但是如果在了解或者發現tableSizeFor方法的話,你就會知道此方法會改變我們的輸入長度 (如果我們輸入15,他會改為16),那么他為什么要修改我們設置的長度,以及修改后有什么作用?帶着這個疑問我們往下看;
1. HashMap 的長度為什么需要是2的冪次方
為了能讓hashMap存取高效,盡量減少碰撞,也就是要盡量把數據分配均勻。
Hash值的取值范圍-2147483648到2147483647,總共有40+億個映射空間,只要哈希函數映射的比較均勻,一般應用很難出現碰撞,但是內存肯定不能一次加載這么長的數組,所以這個散列值是不能拿來直接用的,我們只能創建合理長度的數組作為哈希表,在插入數據之前做取模運算,得到的余數就是將要存放的數據在哈希表中對應的下標。在HashMap中這個下標的取值算法是:(n - 1) & hash
n是哈希表的長度。
取模運算中如果除數是2的冪次方則等價於 其與除數減一的&
操作,就是:hash % length == hash & (length - 1)
采用二進制位操作 &
相對於 %
能夠提高運算效率,這也就解釋了為啥HashMap的長度需要為2的冪次方
2. HashMap怎么實現的
先看下JDK8的源碼:
/**
* 方法保證了HashMap的哈希表長度總位2的冪次方
* 返回大於輸入參數且最近的2的整數次冪的數
*/
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;
}
我也是看到這個方法后才決定寫這篇博文來記錄一下我的感想,我已經不知道多少次被源碼作者所折服,我甚至覺得這才是代碼應該的樣子;
結果分析
n第一次右移一位時,相當於將最高位的1右移一位,再和原來的n取或,就將最高位和次高位都變成1,也就是兩個1;
第二次右移兩位時,將最高的兩個1向右移了兩位,取或后得到四個1;
依次類推,右移16位再取或就能得到32個1;
你是不是不知道要32個1干嘛?自己體會如下:
2的冪次方 | 二進制表示 | 十進制標識 |
---|---|---|
2^0 | 1 | (1-1)+1 |
2^1 | 10 | (2-1)+1 |
2^2 | 100 | (4-1)+1 |
2^3 | 1000 | (8-1)+1 |
2^4 | 10000 | (16-1)+1 |
2^5 | 100000 | (32-1)+1 |
二進制數字如果都是1的話,那么他加一后就是首位為1其他位都是0,這個數字肯定是2的冪次方, 2^n == 1<<n
舉例說明:
10的二進制是1010,減1就是1001
第一次右移取或: 1001 | 0100 = 1101 ;
第二次右移取或: 1101 | 0011 = 1111 ;
第三次右移取或: 1111 | 0000 = 1111 ;
第四次第五次同理
最后得到 n = 1111 ,返回值是 n+1 = 2 ^ 4 = 16 ;
自己實驗一把
static final int tableSizeFor(int cap) {
int n = cap - 1;
n |= n >>> 1;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 2;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 4;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 8;
System.out.println(Integer.toBinaryString(n));
n |= n >>> 16;
System.out.println(Integer.toBinaryString(n));
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
public static void main(String[] args) {
int a = 65538;
System.out.println(Integer.toBinaryString(a));
int i = tableSizeFor(a);
System.out.println(i);
}
執行結果:
10000000000000010
11000000000000001
11110000000000001
11111111000000001
11111111111111111
11111111111111111
131072
看到這里是不是也有中被折服的感覺?那就對了,路還長,編碼還要繼續,且看且學習。。。 _