Java String 类型的HashCode 源码分析
同样是这几天看 HashMap的源码 当 key类型是String类型的时候,需要求出key的hashCode
public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
其中 第二段代码 是 key如果不等于null,那么 就用key的hashCode值与Key的hashCode值的高16位做异或操作。因此为了探明 key的hashCode是如何得出的 于是乎咱们继续往下看。这个是 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;
}
这段代码不是很复杂,可以分析一下。
- 第一 从源码中得知,int h=hash 初始值是0 那就是 说 h=0。
- 第二 if (h == 0 && value.length > 0) 这个判断的意思就是,初始值如果是字符串的话 就会进入到这个 判断里。
- 接着 通过 char val[] = value; 将字符串 变成一个一个字符数组。
- 于是 这个循环 映入眼帘
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
分析这个循环可以得出一个这样的 结论,就是 h =31*h+val[i] (字符的Ascii码) 把这个循环展开就会得到一个这样的数学式子
h=31^(n-1)*val[0]+
31^(n-2)*val[1]+…+val[n-1]
那么 意思就是 用 31 作为底数,n(字符串长度)作为指数乘以相应字符的Ascii 码,然后依次累加。这样就会得到了 这个字符串的 hashCode。 - 但是这里还有一个问题就是,为什么 要选择 31 这个数字作为 底数呢? 为什么不是 32? 33? 或者是 10? 在这里我是这么思考的
第一不选择偶数的原因就是 因为hash的宗旨就是为了让数字更加的散列。那么最好选择的这个数字不要被其他数字整除,否则的话就会产生很多的碰撞,因此这也是不选择偶数的一个原因,所以选择的这个数字最好即是质数也是奇数的数字
第二 31这个数字 在计算的过程中可以可以拆解成 31n=32n-n=n>>5-n 这样做得话,就可以将乘法操作优化成底层的移位操作,加快运算的速率
第三 那么 如果是这样的话 7N 也可以优化成 8n-n 5也可以优化成 4*n+n 等等 为什么不选择这样的数字呢? 那是因为 我们要记住 Hash的宗旨就是让数字更加的散列,因此乘以一个相对较大的数字 得到的值就会更加的散列,如果底数仅仅是一个 7 或者是 5的话虽然复合 第一和第二个原因 但是 它却不能避免更多的碰撞。 所以综上所述 选择31 这个数字。