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

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

为什么用31作为乘数?

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


当前回答

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

其他回答

对哈希函数的一个很大的期望是,它们的结果的均匀随机性在哈希(x) % N这样的操作中仍然存在,其中N是一个任意数字(在许多情况下,是2的幂),一个原因是这样的操作通常用于哈希表中确定槽。在计算散列时使用质数乘数会降低乘数和N个共享除数的概率,这将使操作的结果不那么均匀随机。

还有人指出了一个很好的性质,即乘31可以由一个乘法和一个减法来完成。我只是想指出这样的质数有一个数学术语:梅森质数

所有梅森质数都比2的幂小1所以我们可以写成

p = 2^n - 1

用x乘以p:

x * p = x * (2^n - 1) = x * 2^n - x = (x << n) - x

在许多机器上,移位(SAL/SHL)和减法(SUB)通常比乘法(MUL)快。参见Agner Fog的指令表

这就是为什么GCC似乎通过将梅森质数的乘法替换为移位和置换来优化梅森质数的乘法,见这里。

然而,在我看来,如此小的质数对于哈希函数来说是一个糟糕的选择。对于一个相对较好的哈希函数,你会期望在哈希的较高位具有随机性。然而,使用Java哈希函数,对于较短的字符串,在较高的位上几乎没有随机性(在较低的位上仍然具有高度可疑的随机性)。这使得构建高效哈希表变得更加困难。看看这个你用Java哈希函数做不到的小技巧。

一些回答提到,他们认为31适合一个字节是好的。这实际上是无用的,因为:

(1)我们执行移位而不是乘法,因此乘数的大小无关紧要。

(2)据我所知,没有特定的x86指令来将一个8字节的值与一个1字节的值相乘,所以即使是相乘,你也需要将“31”转换为8字节的值。看这里,你将整个64位寄存器相乘。

(127实际上是一个字节所能容纳的最大梅森素数。)

较小的值是否会增加中下位的随机性?也许吧,但这似乎也大大增加了可能的碰撞:)。

人们可以列出许多不同的问题,但它们通常可以归结为两个未能很好地实现的核心原则:混乱和扩散

但是速度快吗?可能吧,因为它没什么用。然而,如果性能真的是这里的重点,那么每个循环一个字符是相当低效的。对于更长的字符串,为什么不每次循环迭代4个字符(8字节),就像这样?好吧,这将很难与当前定义的哈希,你需要单独乘以每个字符(请告诉我如果有一点hack来解决这个问题:D)。

根据Joshua Bloch的《Effective Java》(这本书再怎么推荐都不为过,多亏了stackoverflow上不断的提及,我才买了这本书):

选择值31是因为它是一个奇质数。如果它是偶数并且乘法溢出,信息就会丢失,因为乘2相当于移位。使用质数的优势不太明显,但它是传统的。31的一个很好的属性是乘法可以被移位和减法代替,以获得更好的性能:31 * i == (i << 5) - i。现代虚拟机自动进行这种优化。

(摘自第3章第9项:重写equals时总是重写hashcode,第48页)

Bloch并没有深入研究这个问题,但我总是听到/相信这是基本的代数。哈希可以归结为乘法和模运算,这意味着如果可以的话,永远不要使用带公因式的数字。换句话说,相对素数提供了答案的均匀分布。

使用哈希的数字通常是:

你放入的数据类型的模量 (2^32或2^64) 哈希表中桶数的模数(变化。在java中,以前是质数,现在是2^n) 在混合函数中乘以或平移一个神奇的数字 输入值

实际上,您只能控制其中的几个值,因此需要多加注意。

You can read Bloch's original reasoning under "Comments" in http://bugs.java.com/bugdatabase/view_bug.do?bug_id=4045622. He investigated the performance of different hash functions in regards to the resulting "average chain size" in a hash table. P(31) was one of the common functions during that time which he found in K&R's book (but even Kernighan and Ritchie couldn't remember where it came from). In the end he basically had to choose one and so he took P(31) since it seemed to perform well enough. Even though P(33) was not really worse and multiplication by 33 is equally fast to calculate (just a shift by 5 and an addition), he opted for 31 since 33 is not a prime:

剩下的 第四,我可能会选择P(31),因为它是RISC上最便宜的计算方法 机器(因为31是2的2次幂之差)P (33) 同样,计算起来很便宜,但它的性能稍微差一些,并且 33是合数,这让我有点紧张。

因此,推理并不像这里的许多答案所暗示的那样理性。但我们都善于在做出直觉决定后提出理性的理由(甚至布洛赫也可能倾向于这样)。

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

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