Java 中所有的类都继承自 Object 类,Object 类中有个返回 hashCode 的本地方法。
public native int hashCode(); 复制代码
在文档的注释中很清楚的说明了 hashCode 的作用,和它应该满足的一些要求。
作用:给一个对象返回一个 hashCode 值,这个值在 hash table 的数据结构中有重要的作用。例如,确定放置在 hash table 哪个索引位置,hash 冲突的频率。
通常的 hashCode 生成方法是将对象的内存地址转换成一个整型数,这样就能为不同的对象返回一个不一样的 hashCode。但是这种方法不能满足上面的第二个条件,所以这种实现也不是 Java 语言所必须的实现方法。
String 类也是继承自 Object 类,它重写了 hashCode() 方法。
/** Cache the hash code for the string */ private int hash; // Default to 0 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; } 复制代码
在 String 中计算的 hashCode 会存储在 hash 变量中,并且只会计算一次。因为 String 是 final 的,并且一个 String 对象被初始化后无法修改,所以它的 hashCode 不会变化。
for 循环计算 hashCode 的方法是根据以下式子:s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]。
31 是一个质数(Prime number),质数也称为素数。质数是大于 1 的自然数,且只能被 1 和其本身整除。
选择 31 的原因大概有以下几点:
一个数乘质数后的结果,只能被 1 、质数、乘数还有结果本身整除。计算 hashCode 选择一个优质质数可以降低 hash 的冲突率。
31 (2 << 5 - 1),在计算的过程中可以被 JVM 优化。
相信第二点很多同学都能够理解,现在解释一下第一点。
我们列举一下 100 以内左右的质数:2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97。
从上面的质数中选择三个小中大质数:2,31,97。分析公式 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] 中的每一项,都是一个数乘质数的平方项。所以我们计算一下每个质数的 n 次方,我们选择 n = 5。那么结果如下:
质数 | 结果 |
---|---|
2 | 2^5 = 32 |
31 | 31^5 = 28,629,151 |
97 | 97^5 = 8,587,340,257 |
可以看到通过质数 2 计算后的 hashCode 值是一个比较小的值,而通过质数 97 计算后的 hashCode 是一个比较大的值,而 31 比较适中。
我们可以认为 hashCode 的范围若太小,可能会增加 hash 冲突的概率。而计算 hashCode 的计算乘子太大容易导致整型数的溢出(这里并不是说选择 31 不会导致溢出,是指一个导致溢出的速率),从而也会导致 hash 冲突的概率。31 可以有效的减轻这两点。
更详细的内容可以看一下 stackoverflow 上面的这个问题: Why does Java's hashCode() in String use 31 as a multiplier?
根据《Effective Java》第二版中的第 9 条,对于我们自己编写的类,覆盖 equals 方法时需要覆盖 hashCode 方法。原因在前面说过。
那么如何设计一个 hashCode 算法,书中设计了一个算法: