HashMap 初始化容量
// 默認容量 16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16
爲什麼必須是2的n次冪
因爲只有是2n,纔可以通過 hash & (leng-1) 計算出的索引儘可能保證數據分佈均勻.
如果不是2的n次冪,計算出的索引特別容易相同,很容易發生hash碰撞,導致其餘數組空間很大程度上沒有存儲數據,鏈表或者紅黑樹過長,效率較低.
說明:爲什麼是2的n次方?
2n的二進制是一個首位是1 後面爲是0的數,如 23 二進制爲00001000 23-1 二進制爲00000111
hash & (2n-1) 表示 hash值對應的二進制與n個1 做與運算 都爲1 則爲1 否則爲0
只有當 一個數的二進制有效位全是1的情況下 ,不同hash值計算的結果差異性纔會更多。
如: 以下結果很明顯全爲1的情況計算的差異性會更大
1111111 100000
&001001 &001001
———————————————
001001 000000
如果在實例化的時候傳入的值不是2n會怎樣?
當實例化HashMap實例時,如果給定number不是2n initialCapacity(number),由於HashMap的capacity必須是2的次冪,會通過以下方法 (tableSizeFor ) 計算找到大於等於number最小的2n的數。
計算容量分析
HashMap map = new HashMap<>(10)
//獲取大於等於傳入容量的最小二次冪值
static final int tableSizeFor(int cap) {
int n = cap - 1; // n = 9
n |= n >>> 1; // 即 n = n | n >>> 1; 結果13
n |= n >>> 2; // n = 15
n |= n >>> 4; // n =
n |= n >>> 8;
n |= n >>> 16;
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
}
// 代碼分析
cap - 1 的操作是爲了規避如果傳入的cap值本身就是2的n次方類型的數時的計算結果
cap = 10; // 計算過程(二進制)
n = cap-1; 0000 1001
———————————————————————————————————————————————
n |= n>>>1; 0000 1101
| 0000 0100
————————————————————
0000 1101
———————————————————————————————————————————————
n |= n>>>2; 0000 1101
| 0000 0011
————————————————————
0000 1111
———————————————————————————————————————————————
n |= n>>>4; 0000 1111
| 0000 0000
————————————————————
0000 1111
———————————————————————————————————————————————
.......
對於 0000 1111 這個數右移8位和右移16位都已經沒有任何意義,最終結果都是0000 1111
———————————————————————————————————————————————
n = n + 1 0000 1111
+ 0000 0001
————————————————————
0001 0000 = 16
結論
以上一系列或運算的目的就是將 cap - 1 的二進制有效位全部變成1
最後再用這些有效位是1的二進制+1 ,就會變成一個大於等於傳入容量的最小2的n次冪的數
注:容量最大也就是32bit的正數,因此最後n|n>>>16,最多也是32個1(這是個負數,在執行tableSizeFor之前做了判斷,如果大於最大的則取最大值)
public HashMap(int initialCapacity, float loadFactor) {
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal initial capacity: " +initialCapacity);
if (initialCapacity > MAXIMUM_CAPACITY)
initialCapacity = MAXIMUM_CAPACITY; // 1 << 30
if (loadFactor <= 0 || Float.isNaN(loadFactor))
throw new IllegalArgumentException("Illegal load factor: " + loadFactor);
this.loadFactor = loadFactor;
this.threshold = tableSizeFor(initialCapacity);
}