hashmap中put(K,V)方法,最终会执行putVal()方法,而在putVal()方法中会执行hash()方法;
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
// 判断key是否为null, 如果为null,则直接返回0;
// 如果不为null,则返回(h = key.hashCode()) ^ (h >>> 16)的执行结果
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
hash()方法把获取到的key的hashCode值再进行计算得到一个hash值,然后将(数组的长度-1)和hash值进行按位与操作:i = (n - 1) & hash得到元素在数组中存放的位置。
为什么不直接使用hashCode值与n-1进行按位与操作?
当n的长度比较小时,n-1的二进制高16位数全部都为0,只能利用低16位,会增加hash冲突的可能性,因为不同的hashCode值的二进制数的低16位数可能相同这样就会导致与n-1进行计算后的结果相同,导致存储的索引位置相同而发生hash冲突。
当我们使用进行 h ^ (h >>> 16) 操作时,会将h的高16位数据和低16位数据进行异或操作,最终得出的hash值的高16位保留了h值的高16位数据,而hash值的低16数据则是h值的高低16位数据共同作用的结果。所以即使h1和h2的低16位相同,最终计算出的hash值低16位也大概率是不同的,降低了hash冲突发生的概率。
这里面还有一个值的注意的点: 为什么是(n-1)?
HashMap的规定数组长度n为2的幂次方,那么n的二进制数的末尾一定为0与hash进行按位与操作最后一位就一定为0。这就导致元素存储索引位置都为偶数,使得数组中的奇数位为空,增加的hash冲突的可能性。
当使用n-1的时候得到一个奇数,二进制数的末尾就是1与hash进行按位与操作后最后一位可能为0也可能是1。充分利用了存储的数组的空间,降低了hash冲突。



