public HashMap(int initialCapacity, float loadFactor)
核心邏輯:根據 initialCapacity 計算 大於等於該值的最小 2 的 N 次方 作為實際桶數組容量。
因為 index = (n - 1) & hash 取模運算要求 n 為 2 的冪,否則分佈不均。詳細證明見 此文。
int capacity = 1;
while (capacity < initialCapacity)
capacity <<= 1;
2 * N 次操作(N 為目標冪次)。initialCapacity > 64 時效率明顯低於新版。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 = cap - 1 → 低位至少有一個 1 變為 0,其餘不變。n |= n >>> 1 → 高位 1 向右傳播一位,變為 11xxxxxx。n |= n >>> 2 → 傳播兩位 → 1111xxxx。n |= n >>> 4 → 11111111(所有低位變 1)。>> 16 覆蓋 32 位 int 範圍。n + 1 → 進位變為 100000000,即下一個 2 的冪。例 cap = 1000(二進制),n = 111,位運算後仍為 111,+1 還原為 1000,完美支援。
| 版本 | 操作類型 | 初始容量 2^N 時操作次數 |
|---|---|---|
| JDK 1.7 | 比較 + 位移 | 2 × N |
| JDK 1.8 | 減法 + 5 次位移/或運算 | 固定 ~11 次 |
結論:當指定容量 > 64(2^6)時,JDK 1.8 算法顯著更快。
static final int tableSizeFor(int cap) {
int n = cap - 1;
if ((cap & n) == 0) { // 經典判斷 2 的冪:n & (n-1) == 0
return (cap >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : cap;
}
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;
}
避免對已經是 2 的冪的輸入做無用位運算,極小優化但體現工程嚴謹。