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 = 10 | 1 = 10 | 0 = 0

>>>

表示右移,用0 补位,例:

111 >>> 1 = 011111 >>> 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 + 1MAXIMUM_CAPACITY 谁更大,谁大取谁

最后的 n + 1 将计算的结果变成 2 的次方数。

结论

这个方法就是输出一个与输入值相邻的 2 的次方数。

posted @ 2022-07-25 13:29  qmgta  阅读(71)  评论(0编辑  收藏  举报