什么是哈希码计算的合理素数?

时间:2009-12-02 21:35:00

标签: java hashcode primes

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的历史无关,而是关于什么使用相同的基本模板在新代码中会有更好的价值。那里的答案都没有尝试回答。)

6 个答案:

答案 0 :(得分:71)

我建议使用 92821 。这就是原因。

要对此给出有意义的答案,您必须了解ij的可能值。我唯一能想到的是,在许多情况下,小值比大值更常见。 (在程序中出现的值为15的几率要比438281923好得多。)因此,通过选择合适的素数使最小的哈希码冲突尽可能大,这似乎是一个好主意。对于31这相当糟糕 - 已经为i=-1j=31提供了与i=0j=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 ,结果非常相似。