为什么HashMap中 hash值是h = key.hashCode() ^ (h >>> 16)

1.源码

static final int hash(Object key) {

        int h;

        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);

}

2.为什么不直接使用hashcode
首先:

hashmap中求key所在数组下标的方法为:

h(key) & (length-1)

参数说明:

h(key) 即根据key计算的hash值

length 即数组长度

& 运算:两个位都为1时,结果才为1

再者:

因为hashmap的数组长度都是2的倍数,所以

以length=8 为例,length-1 的二进制是:

0000 0000 0000 0000 0000 0000 0000 0111

以length=16 为例,length-1 的二进制是:

0000 0000 0000 0000 0000 0000 0000 1111

这里因为是int 4字节,32位,将前面补0方便查看。

可以看到前面的都是0,后面的都是 1,而进行& 运算,只有两个位都是1时,才为1,

例如:

0000 0101 1101 0011 0011 0100 1001 1001

0000 0000 0000 0000 0000 0000 0000 0111 (以length=8 为例)

上面是一个hash值,下面是length-1,两者&运算,

其实只需要看最后面三位,因为前面都是与0比较,都是0,后面计算得到:

001,则在数组中的index为1,

从这里可以看出,index的值 是0到7,即 000 到 111,和取余运算结果相同。

小结:

那么为什么hash值要 参与 h >>> 16 呢,

在上面的例子中,可以看到,参与计算的hashcode值的高位很多都浪费了,只有右侧的几个低位参与了实际计算。

而使用 ^ h >>> 16 的目的就是为了让hash值的高位也参与计算

继续说明:

首先要知道^ 和 >>> 两个位运算的意义,

^ 异或 :两个位相同为0,不同为1。

>>> 无符号右移。

即是说:对于比 2的16次方小的数字,这行代码是没有意义的。

比如 a=1000,  a^ (a>>>16) 还是a,即 计算完了 a还是等于1000,

因为 a >>> 16之后就是0,和0 做 ^ 运算,结果还是原值。

所以 ^ h >>> 16 是针对 key的hashcode 大于2的16次方的情况

通常情况下,使用 char 或较小的数值 作为key时,这行代码没什么意义。

但是,使用字符串,对象等 作为key时,这行代码是很有效的。

比如:

key = "fsfsd"

"fsfsd".hashCode()结果为:97726794

转二进制:

0000 0101 1101 0011 0011 0001 0100 1010

0000 0000 0000 0000 0000 0101 1101 0011 (97726794 >>> 16)

0000 0101 1101 0011 0011 0100 1001 1001 (97726794 ^ (97726794 >>> 16))

        这样,我们看到,hashcode右侧的高位经过>>> 16 之后成为了低位,在以后参与计算数组下标的时候,就含有高位的数值了。

        如果不进行这种方式的调整,对于 hashcode大于2的16次方的值,参与计算的只有左侧的16个低位,这样就比较容易造成hash冲突,使用了这种调整,数据能够更加均匀的找到对应的数组下标。

注意

        为什么是16这个数值呢?不是 >>>8 或者什么其他数字,这个就是经验了,通常使用hashMap存储数据,数组长度是控制在2的16次方之内的,如果是更小的数字,则没有必要了,在知道数据key大概数量的情况下,初始化HashMap时指定容量是比较好的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值