要想了解这么设计的目的,我们需要从ConcurrentHashMap的根据下标获取对象的算法来看,在putVal方法中1018行
(f = tabAt(tab, i = (n - 1) & hash)) == null
通过(n-1) & hash来获得在table中的数组下标来获取节点数据,【&运算是二进制运算符,1 & 1=1,其他都为0】
假设我们的table长度是16, 二进制是【0001 0000】,减一以后的二进制是 【0000 1111】
假如某个key的hash值=9,对应的二进制是【0000 1001】,那么按照(n-1) & hash的算法
0000 1111 & 0000 1001 =0000 1001 , 运算结果是9
当我们扩容以后,16变成了32,那么(n-1)的二进制是 【0001 1111】
仍然以hash值=9的二进制计算为例
0001 1111 & 0000 1001 =0000 1001 ,运算结果仍然是9
我们换一个数字,假如某个key的hash值是20,对应的二进制是【0001 0100】,仍然按照(n-1) & hash算法,分别在16为长度和32位长度下的计算结果
16位: 0000 1111 & 0001 0100=0000 0100
32位: 0001 1111 & 0001 0100 =0001 0100
从结果来看,同样一个hash值,在扩容前和扩容之后,得到的下标位置是不一样的,这种情况当然是不允许出现的,所以在扩容的时候就需要考虑,
而使用高低位的迁移方式,就是解决这个问题.
大家可以看到,16位的结果到32位的结果,正好增加了16.
比如 20 & 15=4 、20 & 31=20 ; 4-20 =16
比如 60 & 15=12 、60 & 31=28; 12-28=16
所以对于高位,直接增加扩容的长度,当下次hash获取数组位置的时候,可以直接定位到对应的位置。
这个地方又是一个很巧妙的设计,直接通过高低位分类以后,就使得不需要在每次扩容的时候来重新计算hash,极大提升了效率。