HashMap存储位置计算

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

 

 

你可能感兴趣的:(HashMap)