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时指定容量是比较好的。