恢复哈希码" sum"

时间:2017-11-04 09:22:04

标签: java math integer integer-overflow

因此,我不是每次都计算哈希码,而是认为我可以保留一个更新所有更改的整数。想象一下有助于哈希码的属性的setter:

public void setFoo(Foo newFoo){
    this.hashCalculator.remove(this.foo.hashCode()); // remove old hash code
    this.hashCalculator.add(newFoo.hashCode()); // add new hash code
    this.foo = newFoo; // set new foo
}

(我希望我没有做一些愚蠢的事情)我认为这将是简单的数学,但我没有实现它。我认为它与溢出的整数有关,但我不确定。显然,我错过了一些东西。分部的其余部分应该加回到值,对吗?

这是我的代码。结果应该是1,但这不是我得到的。

class HashCode
{
    public int value = 1;

    public void add(int val){
        // as suggested by effective java
        value = value * 37 + val;
    }

    public void remove(int val){
        value = (value - val) / 37;
    }
}

HashCode o = new HashCode();

for(int a = 0; a < 1000; a++){
    o.add(a);
}

for(int r = 0; r < 1000; r++){
    o.remove(r);
}

System.out.println(o.value); // should be 1

2 个答案:

答案 0 :(得分:4)

首先:没有办法正确地反转导致整数溢出的操作。基本上问题是您无法知道整数溢出是否在前一个操作中发生过一次,两次甚至更多次。因此,在最后一次哈希计算之前,您无法获得原始value

第二:哈希计算取决于应用哈希值的顺序。

((1 * 37 + 0) * 37 + 1) * 37 + 2 = 50692
((1 * 37 + 2) * 37 + 1) * 37 + 0 = 53428
           ^         ^         ^ the hash values.

由于附加最后一个值的哈希值更改取决于所有以前的哈希值,您不能只更改一个中间哈希值,因此没有(表现良好)方法可以消除前一个示例中1的影响在未来的所有计算中都有。

如果您使用123等而不是1000来测试您的循环,这应该是显而易见的。如果没有整数溢出,只有在按照添加它们的相反顺序删除哈希值时,循环才会起作用。这是一种在“现实生活”中应用没有意义的限制。 @JB Nizet在his comment中写的内容在我看来是正确的。

答案 1 :(得分:3)

基本上,当用整数计算时,你做算术模2 ^ 32。因此,如果溢出,它可以工作,如果不是除以37,而是乘以其模块化反转-1857283155。例如,这给出了结果1:

    int pInv = -1857283155;
    int v = 1;

    for (int a = 0; a < 1000; a++) {
        v = v * 37 + a;
    }

    for (int r = 999; r >= 0; r--) {
        v = (v - r) * pInv;
    }

    System.out.println(v);

第二个问题是,添加a然后b时的哈希值与添加b然后a时的值不同。使用此哈希函数无法解决此问题。