更新
例如,假设我们有一个想要作为多重集处理的数组。
因此,您必须在所有条目出现时对其进行处理,不能使用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(),您应该测试它是否正常工作。你应该衡量你的程序的性能,也许你会发现简单的加法就足够了。否则,我会选择 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 次发生冲突)。但是,我并不认为我的例子与现实生活有很大的相关性。