ConcurrentHashMap-扩容分析拾遗
## 前言
这是一篇对 transfer 方法的拾遗,关于之前那篇文章的一些一笔带过,或者当时不知道的地方进行回顾。
疑点 1. 为什么将链表拆成两份的时候,0 在低位,1 在高位?
回顾一下 transfer 的相关代码:
1 | int runBit = fh & n; |
关键看上面注释的代码,如果 runBit 是 0,那么就设置在低位节点,反之,如果是 1,设置在高位。
为什么这么设计呢?当时楼主一笔带过,称之为这个貌似没有什么特殊含义
,实在是愚蠢之极。
今天解释一下。
这要从 ConcurrentHashMap 的取于下标算法开始说起。
我们知道,在 putVal 方法中,会通过取于对象的 hash 值获取下标。具体代码如下:
1 | else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) { |
也就是 (n - 1) & hash)
,这个 n 就是 length。这个其实相当于 hash % n(n 必须是2的指数
)。但是比 % 更高效。
复习一下与运算:第一个操作数的的第n位于第二个操作数的第n位如果都是1,那么结果的第n为也为1,否则为0
.
然后开始推导:
1 | (n - 1) & hash),取于算法。 |
现在明白了吧?0 在低位,1 在高位不是随便设计的。这里让我想到了一致性 hash 算法:当桶的数量变化了,那么 hash 的位置也会变化
。
这里的设计是为了防止下次取值的时候,hash 不到正确的位置。
实际上,JDK 1.8 的 HashMap 也是这么实现的重新散列。文章深入理解 HashMap put 方法(JDK 8逐行剖析)。其中 resize 方法和这里高度类似。
疑点 2:为什么会有 i >= n || i + n >= nextn 的判断?
回顾一下代码:
1 | if (i < 0 || i >= n || i + n >= nextn) { |
这个判断在当时看来是没有可能存在的。到现在也没明白为什么。。。。
如果有大佬知道,请指点一二。
ConcurrentHashMap-扩容分析拾遗
http://thinkinjava.cn/2018/05/16/2018/2018-05-16-ConcurrentHashMap-扩容分析拾遗/