标准实现很弱,使用它会导致不必要的冲突。想象一个
class ListPair {
List<Integer> first;
List<Integer> second;
ListPair(List<Integer> first, List<Integer> second) {
this.first = first;
this.second = second;
}
public int hashCode() {
return Objects.hashCode(first, second);
}
...
}
现在,
new ListPair(List.of(a), List.of(b, c))
和
new ListPair(List.of(b), List.of(a, c))
具有相同的hashCode,即31*(a+b) + c,因为这里重用了用于List.hashCode 的乘数。显然,碰撞是不可避免的,但产生不必要的碰撞只是......不必要的。
使用31 并没有什么特别聪明的地方。乘数必须为奇数以避免丢失信息(任何偶数乘数至少会丢失最高有效位,四的倍数会丢失二,等等)。任何奇数乘数都是可用的。小的乘法器可能会导致更快的计算(JIT 可以使用移位和加法),但考虑到乘法在现代 Intel/AMD 上只有三个周期的延迟,这无关紧要。小的乘数也会导致小输入的更多冲突,这有时可能是个问题。
使用素数是没有意义的,因为素数在环 Z/(2**32) 中没有意义。
所以,我建议使用随机选择的大奇数(随意取质数)。由于 i86/amd64 CPU 可以对适合单个有符号字节的操作数使用更短的指令,因此对于像 109 这样的乘法器有微小的速度优势。为了最大限度地减少冲突,请使用 0x58a54cf5 之类的东西。
在不同的地方使用不同的乘数是有帮助的,但可能不足以证明额外的工作是合理的。