这个方法的移位太复杂,有些难看懂,就看看文档上是怎么说的:
/**
* Applies a supplemental hash function to a given hashCode, which
* defends against poor quality hash functions. This is critical
* because HashMap uses power-of-two length hash tables, that
* otherwise encounter collisions for hashCodes that do not differ
* in lower bits. Note: Null keys always map to hash 0, thus index 0.
*/
大致意思是这样的:
使用一个hash方法用来改善原先hashCode方法低质量的问题。
这个是很关键的,因为HashMap使用的数组长度是2的次幂,在低位相同的话会产生碰撞。
至于null值,他一直就是放在数组的第一个的
带着这个注释,我们来看看他是怎么通过hash方法来改善HashCode的低质量问题的,怎么降低低位的相同情况,尽量减少碰撞。
1.先来分析一下map中数组的长度length。这个长度是规定要2的次幂的,这就有一个特点,将长度转化成二进制后,不管是多少,总是有一个位上是1,然后其他位上全是0。
这个时候length-1,这个位之后的数字就全是1了。比如过64,二进制是0100 0000,那么他减一就是0011 1111。length先说到这里。
2.再来看下hash方法的实现
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
实在是太复杂了,测试一下把。
public class HashMapSource_hash {
public static void main(String[] args) {
transform(64);
transform(64+32+16);
transform(1+2+4+8);
}
static void transform(int h){
System.out.println(Integer.toBinaryString(h)+"-->"+Integer.toBinaryString(hash(h)));
}
static int hash(int h) {
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
}
打印结果是这样的:
1000000-->1000100
1110000-->1110111
1111-->1111
这个方法的作用大致就是让低位0多的数在低位也得到几个1,这样有什么好处呢?
结合1来看,length-1的值是全是1的,这样2个进行&运算
如果length比较小,比如说是4,那么length-1的二进制为0000 0011,对于一些hashCode稍微高一点的数,如64.128,他们跟length-1与,得到的都是0,这样就发生了冲撞。这个&的好处就是既保证了速度,又保证了计算后得到的值是在length之内的。
indexFor方法做的就是这个事。
static int indexFor(int h, int length) {
return h & (length-1);
}
—————————————————————————————————————————————————————————————
再来看看jdk1.8的hash方法
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里传递的是一个对象,比之前也简洁了很多
如果是空,返回0,如果不是空,返回hashCode与上hashCode无符号右移16位
hash是int类型的,int类型的最大值的二进制是31个1加上前面一个0,无符号右移16位就是舍弃了它一半的低位,把高位给搬到低位来了
这个方法要做的事就如下图: