谈谈HashMap的hash()方法巧妙之处

笔者个人理解,不正之处,欢迎指正与讨论。

先看看JDK1.8中hash算法的实现,感觉真的很巧妙。

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }
index = (n - 1) & hash(key) //n表示长度

如果是自己实现hash算法的话,最简单的话就是直接用hasCode对取余

index = key.hasCode() % n

在HashMap的实现中要求n的长度为2的n次幂
对于2的n次幂取余,可以用更加高效的方法

index = key.hasCode() & (n-1)

上面两种方法都存在一种缺陷,就是取余的计算结果对高位是无效的,只是对低位有效,当计算出来的hasCode()只有高位有变化时,取余的结果还是一样的。

例如

int hashCode1 = 01110101
int hasdCode2 = 01010101

int index1 = 01110101 & 1111 -> 0101
int index2 - 01010101 & 1111 -> 0101

//十进制翻译
int hashCode1 = 117
int hashCode2 = 85

int index1 = 117 % 16  -> 5
int index2 = 85 % 16  -> 5

从上面的例子可以看出来,当key计算出来的hashCode()只有高位变化时,最终算出来的index索引就会引起hash冲突,如果冲突太多的话,HashMap的效率就会非常低下了。

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

再来看看这段代码,对key的hashCode值进行再一次计算。在java中,hashCode是32位的。

首先,对hashCode进行16位的无符号右移。

(我们的例子就假设hashCode是8位的)

int hashCode1 = 01110101 >>> 4
--> hashCode1 = 00000111

int hasCode2 = 01010101 >>> 4
-->hasCode2 = 00000101

然后,对自身进行与或运算。

hashCode1 = 01110101 ^ 00000111
--> hashCode1 = 01110010

hashCode2 = 01010101 ^ 00000101
--> hashCode2 = 01010000

最后,取余

hashCode1 = 01110010 & 1111 = 0010
hashCode2 = 01010000 & 1111 = 0000

通过上面的分析,hash的再次计算能够把高位的变化影响到了低位的变化,真的很神奇啊
一句神奇的代码,大大减少了hash冲突,牛逼!

不正之处,欢迎指正。

你可能感兴趣的:(谈谈HashMap的hash()方法巧妙之处)