HashMap产生hash碰撞一般说法是key的hashCode值一样,其实这种说法是不严谨的,准确说是计算的底层数组下标值一样(废话),当然,key的hashCode的值一样是最明显的原因,而hashCode值不同也会导致hash冲突。
HashMap为了存取高效,要尽量较少碰撞,就是要尽量把数据分配均匀,每个链表长度大致相同,这个实现就在把数据存到哪个链表中的算法;
这个算法实际就是取模,hash%length(为什么取模呢,假设数组大小是16,那么对16取模,得到的肯定是0-15,刚好可以作为数组下标),计算机中直接求余效率不如位移运算,HashMap对取模做了优化。源码中做了优化: hash&(length-1),hash%length == hash&(length-1)的前提是length是2的n次方,所以容量为2的n次方就是为了取模速度;
JDK7的hash计算:
final int hash(Object k) {
int h = hashSeed;
if (0 != h && k instanceof String) {
return sun.misc.Hashing.stringHash32((String) k);
}
h ^= k.hashCode();
// This function ensures that hashCodes that differ only by
// constant multiples at each bit position have a bounded
// number of collisions (approximately 8 at default load factor).
h ^= (h >>> 20) ^ (h >>> 12);
return h ^ (h >>> 7) ^ (h >>> 4);
}
JDK8的hash计算:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
这里key.hashCode()与key.hashCode()右移16位后做异或运算,解决了什么问题了?
假设下标直接是key.hashCode()&(length-1),则只有hashCode()的低位参与运算(因为hashCode()是32位的,而length-1一般不大,比如容量16,16-1=15,15二进制就是1111,换成32位的话,高位就全是0了,根据&运算规则1&1=1,其他情况都是0,所以hashCode()高位没参与运算了),如果低位不变,只是高位变,key.hashCode()&(length-1)值还是一样,那么就冲突了。
key.hashCode()右移16位就是高位变成了低位,原来高位全补0,,然后做^运算,实际就是原来的hashCode()值的高位与低位做^运算,这就解决了低位不变高位无论变与不变仍冲突的问题,现在无论高位低位哪个变,最终hash值都会变。
至于为啥这里用的是^运算,看下面的运算规则,很明显,^结果相同概率是2/4,而其他两个运算结果相同概率都是3/4,我们目的是尽量让高位低位变化产生的值不一样,所以自然就选^了。
位与(&) : 0 & 0 = 0 0 & 1 = 0 1 & 0 = 0 1 & 1 =1 ;
位异或(^): 0 ^ 0 = 0 0 ^ 1 = 1 1 ^ 0 = 1 1 ^ 1 = 0
按位或(|): 0 | 0 = 0 0 | 1 = 1 1 | 0 = 1 1 | 1 = 1
参考:https://www.jianshu.com/p/e1d3ba0c733a