更新
例如,假设我们有一个想要作为多重集处理的数组。
因此,您必须在所有条目出现时对其进行处理,您不能使用count
,也不能假设条目以已知顺序出现。
我会考虑的一般功能是
int hashCode() {
int x = INITIAL_VALUE;
for (Object o : this) {
x = f(x, o==null ? NULL_HASH : g(o.hashCode()));
}
return h(x);
}
一些观察:
- 正如其他答案中已经说明的那样, INITIAL_VALUE 并不重要。
- 我不会去,
NULL_HASH=0
因为这会忽略空值。
g
如果您希望成员的哈希值在一个小范围内(例如,如果它们是单个字符,则可能会发生这种情况),可以使用该函数。
- 该功能
h
可用于改善结果,这不是很重要,因为这已经发生在例如HashMap.hash(int)
.
- 该函数
f
是最重要的一个,不幸的是,它非常有限,因为它显然必须是关联的和可交换的。
- 该函数
f
在两个参数中都应该是双射的,否则会产生不必要的冲突。
在任何情况下,我都不会推荐f(x, y) = x^y
,因为它会使一个元素出现两次以抵消。使用加法更好。就像是
f(x, y) = x + (2*A*x + 1) * y
其中A
是一个常数满足上述所有条件。这可能是值得的。因为A=0
它退化为加法,所以使用偶数A
并不好,因为它会将位移x*y
出。使用A=1
很好,并且可以使用架构2*x+1
上的单个指令来计算表达式。如果成员的散列分布不均,x86
使用更大的奇数可能会更好。A
如果您选择重要的hashCode()
东西,您应该测试它是否正常工作。你应该衡量你的程序的性能,也许你会发现简单的加法就足够了。否则,我会选择 for NULL_HASH=1
、g=h=identity
和A=1
。
我的旧答案
可能是出于效率原因。对于某些实现,调用count
可能很昂贵,但entrySet
可以改为使用。不过可能更贵,我不能说。
我为 Guava 的 hashCode 和 Rinke 的以及我自己的建议做了一个简单的碰撞基准测试:
enum HashCodeMethod {
GUAVA {
@Override
public int hashCode(Multiset<?> multiset) {
return multiset.hashCode();
}
},
RINKE {
@Override
public int hashCode(Multiset<?> multiset) {
int result = 0;
for (final Object o : multiset.elementSet()) {
result += (o==null ? 0 : o.hashCode()) * multiset.count(o);
}
return result;
}
},
MAAARTIN {
@Override
public int hashCode(Multiset<?> multiset) {
int result = 0;
for (final Multiset.Entry<?> e : multiset.entrySet()) {
result += (e.getElement()==null ? 0 : e.getElement().hashCode()) * (2*e.getCount()+123);
}
return result;
}
}
;
public abstract int hashCode(Multiset<?> multiset);
}
碰撞计数代码如下:
private void countCollisions() throws Exception {
final String letters1 = "abcdefgh";
final String letters2 = "ABCDEFGH";
final int total = letters1.length() * letters2.length();
for (final HashCodeMethod hcm : HashCodeMethod.values()) {
final Multiset<Integer> histogram = HashMultiset.create();
for (final String s1 : Splitter.fixedLength(1).split(letters1)) {
for (final String s2 : Splitter.fixedLength(1).split(letters2)) {
histogram.add(hcm.hashCode(ImmutableMultiset.of(s1, s2, s2)));
}
}
System.out.println("Collisions " + hcm + ": " + (total-histogram.elementSet().size()));
}
}
并打印
Collisions GUAVA: 45
Collisions RINKE: 42
Collisions MAAARTIN: 0
所以在这个简单的例子中,Guava 的 hashCode 表现非常糟糕(63 次可能的冲突中有 45 次)。但是,我并不认为我的例子与现实生活有很大的相关性。