Eclipse 3.5有一个非常好的功能来生成Java hashCode()函数。它会生成例如(稍微缩短:):
class HashTest {
int i;
int j;
public int hashCode() {
final int prime = 31;
int result = prime + i;
result = prime * result + j;
return result;
}
}
(如果类中有更多属性,则为每个附加属性重复result = prime * result + attribute.hashCode();
。对于整数。可以省略.hashCode()。)
这似乎很好但是对于素数的选择31。它可能取自hashCode implementation of Java String,这是出于性能原因而使用的,这些原因在引入硬件乘法器之后很久就消失了。对于i和j的小值,这里有许多哈希码冲突:例如(0,0)和(-1,31)具有相同的值。我认为这是一个Bad Thing(TM),因为经常出现小值。对于String.hashCode,您还会发现许多具有相同哈希码的短字符串,例如“Ca”和“DB”。如果选择一个大素数,如果你选择了素数,这个问题就会消失。
所以我的问题是:选择什么是好的素数?你用什么标准来找到它?
这是一个普遍的问题 - 所以我不想给i和j一个范围。但我认为在大多数应用中,相对较小的值比较大的值更常出现。 (如果你有大的值,素数的选择可能不重要。)它可能没有多大区别,但更好的选择是一种简单明了的方法来改善这一点 - 那么为什么不这样做呢? Commons lang HashCodeBuilder也提出了奇怪的小值。
(澄清:这是不是是Why does Java's hashCode() in String use 31 as a multiplier?的副本,因为我的问题与JDK中31的历史无关,而是关于什么使用相同的基本模板在新代码中会有更好的价值。那里的答案都没有尝试回答。)
答案 0 :(得分:71)
我建议使用 92821 。这就是原因。
要对此给出有意义的答案,您必须了解i
和j
的可能值。我唯一能想到的是,在许多情况下,小值比大值更常见。 (在程序中出现的值为15的几率要比438281923好得多。)因此,通过选择合适的素数使最小的哈希码冲突尽可能大,这似乎是一个好主意。对于31这相当糟糕 - 已经为i=-1
和j=31
提供了与i=0
和j=0
相同的哈希值。
由于这很有趣,我编写了一个小程序,在这个意义上搜索整个int范围以获得最佳素数。也就是说,对于每个素数,我搜索Math.abs(i) + Math.abs(j)
的最小值i,j
,而0,0
的所有值都与i=-25486, j=67194
具有相同的哈希码,然后取最小值为i=-46272 and j=46016
的素数。尽可能大。
Drumroll :在这个意义上最好的素数是486187739(最小的碰撞是Math.sqrt(i*i+j*j)
)。几乎同样好且更容易记住的是92821,最小的碰撞是i=-6815 and j=70091
。
如果你给“小”另一个含义,并希望尽可能大的碰撞-46272,46016
的最小值,结果会有所不同:最好的是{{1}}的1322837333,但是我最喜欢的92821(最小碰撞{{1}})几乎和最佳值一样好。
我确实承认,这些计算在实践中是否有意义是值得商榷的。但我确实认为将92821作为素数比31更有意义,除非你有充分的理由不这样做。
答案 1 :(得分:5)
实际上,如果你把素数大到接近INT_MAX
,你就会因为模运算而遇到同样的问题。如果你希望主要散列长度为2的字符串,那么INT_MAX
的平方根附近的素数可能是最好的,如果你散列的字符串更长,那么无关紧要,碰撞也是不可避免的......
答案 2 :(得分:5)
碰撞可能不是一个大问题......哈希的主要目标是避免使用等于1:1的比较。 如果你有一个实现,其中equals对于有冲突哈希的对象来说“通常”非常便宜,那么这根本就不是问题。
最后,散列的最佳方法取决于您所比较的内容。对于int对(如示例所示),使用基本的按位运算符就足够了(使用&或^)。
答案 3 :(得分:3)
您需要为i和j定义范围。您可以为两者使用素数。
public int hashCode() {
http://primes.utm.edu/curios/ ;)
return 97654321 * i ^ 12356789 * j;
}
答案 4 :(得分:3)
我选择7243.足够大,以避免与小数字碰撞。不会很快溢出到小数字。
答案 5 :(得分:1)
我只想指出hashcode与prime无关。 在JDK实现中
for (int i = 0; i < value.length; i++) {
h = 31 * h + val[i];
}
我发现如果用 27 替换 31 ,结果非常相似。