因此,我不是每次都计算哈希码,而是认为我可以保留一个更新所有更改的整数。想象一下有助于哈希码的属性的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
答案 0 :(得分:4)
首先:没有办法正确地反转导致整数溢出的操作。基本上问题是您无法知道整数溢出是否在前一个操作中发生过一次,两次甚至更多次。因此,在最后一次哈希计算之前,您无法获得原始value
。
第二:哈希计算取决于应用哈希值的顺序。
((1 * 37 + 0) * 37 + 1) * 37 + 2 = 50692
((1 * 37 + 2) * 37 + 1) * 37 + 0 = 53428
^ ^ ^ the hash values.
由于附加最后一个值的哈希值更改取决于所有以前的哈希值,您不能只更改一个中间哈希值,因此没有(表现良好)方法可以消除前一个示例中1
的影响在未来的所有计算中都有。
如果您使用1
,2
,3
等而不是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时的值不同。使用此哈希函数无法解决此问题。