曹工说JDK源码(3)--ConcurrentHashMap,Hash算法优化、位运算揭秘

hashcode,有点讲究

什么是好的hashcode,一般来说,一个hashcode,一般用int来表示,32位。

下面两个hashcode,大家觉得怎么样?

0111 1111 1111 1111 1111 1111 1111 1111 ------A 1111 1111 1111 1111 1111 1111 1111 1111 ------B

只有第32位(从右到左)不一样,好像也没有所谓的好坏吧?

那,我们再想想,hashcode一般怎么使用呢?在hashmap中,由数组+链表+红黑树组成,其中,数组乃重中之重,假设数组长度为2的n次方,(hashmap的数组,强制要求长度为2的n次方),这里假设为8.

大家又知道,hashcode 对 8 取模,效果等同于 hashcode & (8 - 1)。

那么,前面的A 和 (8 - 1)相与的结果如何呢?

0111 1111 1111 1111 1111 1111 1111 1111 ------A 0000 0000 0000 0000 0000 0000 0000 0111 ------ 8 -1 相与 0000 0000 0000 0000 0000 0000 0000 0111 ------ 7

结果为7,也就是,会放进array[7]。

大家再看B的计算过程:

1111 1111 1111 1111 1111 1111 1111 1111 ------B 0000 0000 0000 0000 0000 0000 0000 0111 ------ 8 -1 相与 0000 0000 0000 0000 0000 0000 0000 0111 ------ 7

虽然B的第32位为1,但是,奈何和我们相与的队友,7,是个垃圾。

前面的高位,全是0。

ok,你懂了吗,数组长度太小了,才8,导致前面有29位都是0;你可能觉得一般容量不可能这么小,那假设容量为2的16次方,容量为65536,这下不是很小了吧,但即使如此,前面的16位也是0.

所以,问题明白了吗,我们计算出来的hashcode,低位相同,高位不同;但是,因为和我们进行与计算的队友太过垃圾,导致我们出现了hash冲突。

ok,我们怎么来解决这个问题呢?

我们能不能把高位也参与计算呢?自然,是可以的。

hashmap中如何优化 static final int hash(Object key) { int h; return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

这里,其实分了3个步骤:

计算hashcode,作为操作数1

h = key.hashCode()

将第一步的hashcode,右移16位,作为操作数2

h >>> 16

操作数1 和 操作数2 进行异或操作,得到最终的hashcode

还是拿前面的来算,

0111 1111 1111 1111 1111 1111 1111 1111 ------A 0000 0000 0000 0000 0111 1111 1111 1111 ----- A >>> 16 异或(相同则为0,否则为1) 0111 1111 1111 1111 1000 0000 0000 0000 --- 2147450880

这里算出来的结果是 2147450880,再去对 7 进行与运算:

0111 1111 1111 1111 1000 0000 0000 0000 --- 2147450880 0000 0000 0000 0000 0000 0000 0000 0111 ------ 8 -1 与运算 0000 0000 0000 0000 0000 0000 0000 0000 ------ 0

这里的A,算出来,依然在array[0]。

再拿B来算一下:

1111 1111 1111 1111 1111 1111 1111 1111 ------ B 0000 0000 0000 0000 1111 1111 1111 1111 ----- B >>> 16 异或(相同则为0,否则为1) 1111 1111 1111 1111 0000 0000 0000 0000 --- -65536 0000 0000 0000 0000 0000 0000 0000 0111 ------ 7 与运算 0000 0000 0000 0000 0000 0000 0000 0000 ------- 0

最终算出来为0,所以,应该放在array[0]。

恩?算出来两个还是冲突了,我只能说,我挑的数字真的牛逼,是不是该去买彩票啊。。

总的来说,大家可以多试几组数,下边提供下源代码:

public class BinaryTest { public static void main(String[] args) { int a = 0b00001111111111111111111111111011; int b = 0b10001101111111111111110111111011; int i = tabAt(32, a); System.out.println("index for a:" + i); i = tabAt(32, b); System.out.println("index for b:" + i); } static final int tabAt(int arraySize, int hash) { int h = hash; int finalHashCode = h ^ (h >>> 16); int i = finalHashCode & (arraySize - 1); return i; } }

虽然说,我测试了几个数字,还是有些冲突,但是,你把高16位弄进来参与计算,总比你不弄进来计算要好吧。

大家也可以看看hashmap中,hash方法的注释:

/** * Computes key.hashCode() and spreads (XORs) higher bits of hash * to lower. Because the table uses power-of-two masking, sets of * hashes that vary only in bits above the current mask will * always collide. (Among known examples are sets of Float keys * holding consecutive whole numbers in small tables.) So we * apply a transform that spreads the impact of higher bits * downward. There is a tradeoff between speed, utility, and * quality of bit-spreading. Because many common sets of hashes * are already reasonably distributed (so don't benefit from * spreading), and because we use trees to handle large sets of * collisions in bins, we just XOR some shifted bits in the * cheapest possible way to reduce systematic lossage, as well as * to incorporate impact of the highest bits that would otherwise * never be used in index calculations because of table bounds. */

里面提到了2点:

So we apply a transform that spreads the impact of higher bits downward.

所以,我们进行了一个转换,把高位的作用利用起来。

we just XOR some shifted bits in the cheapest possible way to reduce systematic lossage, as well as

to incorporate impact of the highest bits that would otherwise never be used in index calculations because of table bounds.

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpxspg.html