为什么Java的hashCode()在字符串中使用31作为乘法器?

Why does Java's hashCode() in String use 31 as a multiplier?

通过Java文档的hash码,String摄影对象是A为:

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

using int arithmetic, where s[i] is the
ith character of the string, n is the length of
the string, and ^ indicates exponentiation.

被用来作为一个乘数31是为什么?

我的理解是,在乘数应该是条大素数的数。那么为什么困难或甚至29,37,97?


根据Joshua Bloch的有效Java(一本不能被推荐的书,而我是在StAcExver上不断提到的,买的书):

The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.

(摘自第3章第9项:重写等于时始终重写哈希代码,第48页)


正如Goodrich和Tamassia指出的那样,如果您使用常量31、33、37、39和41来处理50000多个英语单词(形成为两个Unix变体中提供的单词列表的并集),那么在每种情况下,将产生少于7个冲突。知道这一点,许多Java实现选择这些常量中的一个就不足为奇了。

巧合的是,当我看到这个问题时,我正在阅读"多项式散列码"一节。

编辑:这里是我上面提到的~10MB PDF书的链接。有关Java中的数据结构和算法的第10.2部分哈希表(第413页)


在(大部分)旧处理器上,乘以31相对便宜。例如,在ARM上,它只是一条指令:

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

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

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


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

如果不使用2的幂,则也会填充最右边的低位,以便与进入哈希的下一段数据混合。

表达n * 31相当于(n << 5) - n


您可以在http://bug s.java.com/bugdatabase/view_bug.do的"comments"下阅读bloch的原始推理。错误ID=4045622。他研究了不同哈希函数在哈希表中产生的"平均链大小"方面的性能。P(31)是他在K&R的书中发现的那个时期的常见功能之一(但即使是Kernighan和Ritchie也记不清它是从哪里来的)。最后,他基本上必须选择一个,所以他选择了P(31),因为它的表现似乎足够好。尽管P(33)并不是很糟糕,乘33也同样快(只需5的移位加上一个加法),但他选择了31,因为33不是素数:

Of the remaining
four, I'd probably select P(31), as it's the cheapest to calculate on a RISC
machine (because 31 is the difference of two powers of two). P(33) is
similarly cheap to calculate, but it's performance is marginally worse, and
33 is composite, which makes me a bit nervous.

因此,推理并不像这里许多答案所暗示的那样理性。但我们都很擅长在做出决定后提出合理的理由(甚至布洛赫也可能会这么做)。


事实上,37个会很好地工作!z:=37*x可按y := x + 8 * x; z := x + 4 * y计算。这两个步骤都对应于一个LEA x86指令,因此这非常快。

实际上,通过设置y := x + 8 * x; z := x + 8 * y,可以以相同的速度与更大的素数73相乘。

使用73或37(而不是31)可能更好,因为这会导致代码更密集:两个LEA指令只需要6个字节,而移动+移位+相减的7个字节则需要31。一个可能的警告是,这里使用的三参数LEA指令在英特尔的Sandy Bridge体系结构上变慢了,延迟增加了3个周期。

此外,73是谢尔顿库珀最喜欢的数字。


尼尔·科菲解释了为什么31用于消除偏见。

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


在JDK-4045622中,Joshua Bloch描述了选择特定(新)String.hashCode()实现的原因。

The table below summarizes the performance of the various hash
functions described above, for three data sets:

1) All of the words and phrases with entries in Merriam-Webster's
2nd Int'l Unabridged Dictionary (311,141 strings, avg length 10 chars).

2) All of the strings in /bin/, /usr/bin/, /usr/lib/, /usr/ucb/
and /usr/openwin/bin/* (66,304 strings, avg length 21 characters).

3) A list of URLs gathered by a web-crawler that ran for several
hours last night (28,372 strings, avg length 49 characters).

The performance metric shown in the table is the"average chain size"
over all elements in the hash table (i.e., the expected value of the
number of key compares to look up an element).

1
2
3
4
5
6
7
8
9
10
11
12
                          Webster's   Code Strings    URLs
                          ---------   ------------    ----
Current Java Fn.          1.2509      1.2738          13.2560
P(37)    [Java]           1.2508      1.2481          1.2454
P(65599) [Aho et al]      1.2490      1.2510          1.2450
P(31)    [K+R]            1.2500      1.2488          1.2425
P(33)    [Torek]          1.2500      1.2500          1.2453
Vo'
s Fn                   1.2487      1.2471          1.2462
WAIS Fn                   1.2497      1.2519          1.2452
Weinberger's Fn(MatPak)   6.5169      7.2142          30.6864
Weinberger'
s Fn(24)       1.3222      1.2791          1.9732
Weinberger's Fn(28)       1.2530      1.2506          1.2439

Looking at this table, it's clear that all of the functions except for
the current Java function and the two broken versions of Weinberger's
function offer excellent, nearly indistinguishable performance. I
strongly conjecture that this performance is essentially the
"theoretical ideal", which is what you'd get if you used a true random
number generator in place of a hash function.

I'd rule out the WAIS function as its specification contains pages of random numbers, and its performance is no better than any of the
far simpler functions. Any of the remaining six functions seem like
excellent choices, but we have to pick one. I suppose I'd rule out
Vo's variant and Weinberger's function because of their added
complexity, albeit minor. Of the remaining four, I'd probably select
P(31), as it's the cheapest to calculate on a RISC machine (because 31
is the difference of two powers of two). P(33) is similarly cheap to
calculate, but it's performance is marginally worse, and 33 is
composite, which makes me a bit nervous.

Josh


布洛赫并没有深入研究这个问题,但我一直听说/相信的基本原理是,这是基础代数。散列可以归结为乘法和模运算,这意味着如果你能帮助它的话,你永远不想使用带公因数的数字。换句话说,相对质数提供了一个均匀分布的答案。

使用哈希组成的数字通常是:

  • 输入的数据类型的模数(2 ^ 32或2 ^ 64)
  • 哈希表中桶数的模数(变化。在Java中曾经是素数,现在是2 ^ n)
  • 在混合函数中乘以或移位一个幻数
  • 输入值

你只需要控制其中的一些值,所以应该多加注意。


我不确定,但我想他们测试了一些素数样本,发现31对一些可能的字符串样本给出了最佳分布。


在最新版本的JDK中,仍使用31。http://DOCS.Oracle/Eng/Eng/Java/JavaSe/ 11/DOCS/API/Java.Base/Java/Lang/String。

哈希字符串的用途是

  • 唯一(参见hashcode计算文档中的运算符^,它有助于唯一)
  • 计算成本低

31是最大值,可放入8位(=1字节)寄存器。最大的质数可以放在1字节寄存器中,是奇数。

乘31是<<5然后减去本身,因此需要廉价的资源。