根据Java文档,String对象的哈希代码是这样计算的:

S [0]*31^(n-1) + S [1]*31^(n-2) +…+ s (n - 1) 使用int算术,其中s[i]是 字符串的第i个字符,n是的长度 字符串,^表示取幂。

为什么用31作为乘数?

我知道乘数应该是一个相对较大的质数。那么为什么不是29岁,37岁,甚至97岁呢?


当前回答

通过相乘,位向左移位。这使用了更多哈希码的可用空间,减少了冲突。

通过不使用2的幂,低阶,最右边的位也被填充,与进入散列的下一段数据混合。

表达式n * 31等价于(n << 5) - n。

其他回答

我不确定,但我猜他们测试了一些质数样本,发现31在一些可能的字符串样本中给出了最好的分布。

通过相乘,位向左移位。这使用了更多哈希码的可用空间,减少了冲突。

通过不使用2的幂,低阶,最右边的位也被填充,与进入散列的下一段数据混合。

表达式n * 31等价于(n << 5) - n。

事实上,37就可以了!Z:= 37 * x可计算为y:= x + 8 * x;z:= x + 4 * y。这两个步骤都对应一个LEA x86指令,所以这是非常快的。

事实上,通过设置y:= x + 8 * x,可以以同样的速度完成与更大的素数73的乘法运算;Z:= x + 8 * y。

使用73或37(而不是31)可能会更好,因为它会导致更密集的代码:两条LEA指令只占用6个字节,而move+shift+subtract(31的乘法)占用7个字节。一个可能的警告是,这里使用的3参数LEA指令在英特尔的Sandy桥架构上变慢了,延迟增加了3个周期。

而且,73是谢尔顿·库珀最喜欢的数字。

在(大多数)老式处理器上,乘以31可能相对便宜。例如,在ARM上,它只有一条指令:

RSB       r1, r0, r0, ASL #5    ; r1 := - r0 + (r0<<5)

大多数其他处理器都需要单独的移位和减法指令。然而,如果你的乘数很慢,这仍然是一种胜利。现代处理器往往具有快速乘法器,所以只要32在正确的一边,就没有太大区别。

这不是一个很好的哈希算法,但它已经足够好了,比1.0代码更好(比1.0规范好得多!)。

尼尔·科菲解释了为什么在熨平偏差下使用31。

基本上,使用31可以为哈希函数提供更均匀的集位概率分布。