HashMap中的hash算法为解决性能冲突带来的优化

259 阅读3分钟

最近因为刚好被问到,HashMap中hash算法做了什么优化,没回答上来,所以去看了下源码,百度了学习了一下。 首先先看下1.8里面key是如何进行hash操作的

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

关于位异或^的,我在看源码的时候也写了一点点笔记,juejin.cn/user/278878… 我们可以看到,它不是直接拿key的hashCode的,而是做了一层运算,为什么要多这一层操作呢,这个涉及到定位table表的下标的计算了,在下面讲。 我们从这个公式可以看到,这里是对key的hashCode先进行了移位运算,将hashCode向右移动了16位,然后跟原来的hashCode进行了位异或运算。大致的流程如下

hashCode最初的值 1001 0000 0000 1000 0000 0000 0001 1111

进行移位运算后 0000 0000 0000 0000 1001 0000 0000 1000

执行异或运算后 1001 0000 0000 0000 1001 0000 0001 0111

我们这样运算下来,可以看到,因为将原hashcode的高位与低位进行了运算,所以使得低位的变化更加丰富了(这个是重点) 接下来就要看源码中对于key在table下标的定位逻辑了

image.png 就是我框中标出来的那部分,这个实际上还是我们的开放寻址法,是由hash%n对n取余的变形。 前提条件是n是2的幂次方,也就是n=2^x,那么就有(n-1)&hash=hash%n,这里用位运算是因为位运算相对于取余运算要高效很多。 然后我们看下n-1,一般情况下,n的值不会大到哪里取,所以对应的值如下: 0000 0000 0000 0000 0000 0000 0000 0001 //n=2

0000 0000 0000 0000 0000 0000 0000 0011 //n=4

0000 0000 0000 0000 0000 0000 0000 0111 //n=12

0000 0000 0000 0000 0000 0000 0000 1111 //n=16

我们可以观察到,n的值主要是在低位体现,这时候联系到前面对hash的操作,目的是用高位异或来使得hash值得低位变得更丰富,这样在对n-1进行位与运算的时候,得出的值会更加丰富,起到更加有效避免hash冲突的效果。

注意:1.7对hash值的操作实际上是对hashCode进行了4次位运算,1.8变成只有一次,网上说这样是更加效率,质量来考虑。具体的也不太清楚,目前只能简单的理解为,进行一次位运算的时间比较少了。