HashMap 中的 tableSizeFor 方法如何计算 threshold 临界点
概述
我在看 HashMap 源码的时候发现,在设置 initialCapacity 时(也就是设置初始容量 Map<String, String> map = new HashMap<>(1);
),会调用 tableSizeFor() 方法:
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 的容量总是 2 的次方,这样可以按位操作计算容量,提升效率。
代码中的几个关键部分
|
| 表示或运算,有一个 1 结果就为 1,如:
1 | 0 = 1
、0 | 1 = 1
、0 | 0 = 0
>>>
表示右移,用0 补位,例:
111 >>> 1 = 011
、111 >>> 2 = 001
|=
n |= n >>> 1;
等价于
n = n | n >>> 1;
这与 +=
、-=
是相同的
// 这两种写法是等价的
n += 1;
n = n + 1;
看源码
int n = cap - 1;
n |= n >>> 1;
n |= n >>> 2;
n |= n >>> 4;
n |= n >>> 8;
n |= n >>> 16;
这段代码的意义就是找到比输入的数大并且相邻的 2 的次方数
比如输入 3,输出就是 4;输入 5,输出 8。
我们只需要跟着算一下就能明白了,假设这里的 cap 为 10
int n = cap - 1;
结果就是 n = 9
然后将 n(也就是 9)转化为二进制数:1001
然后执行下一行:
n |= n >>> 1;
n >>> 1 就是 1001 右移一位变成 0100
然后将得到的结果与 n 做或运算:1001 | 0100 = 1101
此时 n = 1101
n |= n >>> 2;
n >>> 2 = 0011
1101 | 0011 = 1111
n |= n >>> 4;
n >>> 4 = 0000
0000 | 1111 = 1111
n |= n >>> 8;
n >>> 8 = 0000
0000 | 1111 = 1111
到这里就可以看出来了,这一系列运算将二进制数的所有位变成了 1,而 int 是32 位的,也就是说:计算完最后一步 n |= n >>> 16;
就可以保证覆盖到 int 类型中的所有数
最后一行:
return (n < 0) ? 1 : (n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1;
这里是一个三元运算符,首先判断 n 是否小于 0,是:则返回 1;否:则继续判断 n 是否大于等于 MAXIMUM_CAPACITY
(这是一个设定好的常量),是:则返回 MAXIMUM_CAPACITY
;否:则返回 n + 1
(n >= MAXIMUM_CAPACITY) ? MAXIMUM_CAPACITY : n + 1
这里的 n >= MAXIMUM_CAPACITY
可以理解为 n + 1 > MAXIMUM_CAPACITY
也就是比较 n + 1
与 MAXIMUM_CAPACITY
谁更大,谁大取谁
最后的 n + 1 将计算的结果变成 2 的次方数。
结论
这个方法就是输出一个与输入值相邻的 2 的次方数。