【Java深刻研究】十一、深刻研究hashmap中的hash算法

1、简介

你们都知道,HashMap中定位到桶的位置 是根据Key的hash值与数组的长度取模来计算的。html

JDK8中的hash 算法:java

    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

取模算法:算法

hash(key)&(length-1)

2、深刻分析

一、取模算法为何用的是位与运算?

因为位运算直接对内存数据进行操做,不须要转成十进制,所以处理速度很是快。数组

2的倍数取模,只要将数与2的倍数-1作按位与运算便可。post

对原理感兴趣的能够参考【Java基础】1四、位与(&)操做与快速取模优化

二、为何不直接使用key.hashcode()进行取模运算?

咱们知道hash的目的是为了尽可能分布均匀。spa

取模作位与运算的时候,实际上刚刚开始数组的长度通常比较小,只利用了低16位,高16位是用不到的。这种状况下,产生hash冲突的几率会大大增长。设计

这样设计保证了对象的hashCode的高16位的变化能反应到低16位中,相比较而言减小了hash冲突的状况 。code

选用亦或的方式是由于&和|都会使得结果偏向0或者1 ,并非均匀的概念。xml

三、String的hashCode()深刻分析

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

推导出的公式以下:

s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]

举个例子推导计算一下:

假设 n=3
i=0 -> h = 31 * 0 + val[0]
i=1 -> h = 31 * (31 * 0 + val[0]) + val[1]
i=2 -> h = 31 * (31 * (31 * 0 + val[0]) + val[1]) + val[2]
       h = 31*31*31*0 + 31*31*val[0] + 31*val[1] + val[2]
       h = 31^(n-1)*val[0] + 31^(n-2)*val[1] + val[2]

3.一、为何使用31做为计算的因子呢?

  • 选择质数做为乘子,会大大下降hash冲突的几率。质数的值越大,hash冲突率越低
  • 31参与乘法运算,能够被 JVM 优化,31 * i = (i << 5) - i
  • 使用 101 计算 hash code 容易致使整型溢出,致使计算精度丢失
相关文章
相关标签/搜索