如果有两个布尔字段,如何实现一个好的哈希码?通常人们只是将整数值添加到他们的哈希码值中。但如果我简单地在哈希码中添加 1 或 0,我认为这不好。因为如果我有两个 A 类对象:
obj1.b = true,obj1.c = false。
obj2.b = false,obj2.c = true。
其他都一样。那么这两个不相等的对象的hashcode是相同的。我知道这种情况没问题。但想象一下,如果有 100 个布尔字段,那么碰撞会太多,对吧?我不希望这么多不同的物体落入同一个桶中。
我下面所做的是将不同的数字分配给每个字段的不同真值,因此对象哈希码可以非常不同。
public class A {
private final String a;
private final boolean b;
private final boolean c;
...
@Override public int hashCode(){
int i,j;
if(b) {
i = 10;
}
else {
i = 0;
}
if(c) {
j = 88;
}
else {
j = 3;
}
int result = 0;
result = 31*result + i + j;
result = 31*result + (a != null ? a.hashCode() : 0);
return result;
}
}
您有几个选择:
保证布尔值之间永远不会发生冲突的最佳方法是使用类似于“位标记”中使用的技术,让每个布尔值占据自己的位。例如:
// `byte` can be replaced with `short`, `int`, or `long` to fit all of your variables.
byte booleans = 0;
if(bool1) booleans += 1; // 0001
if(bool2) booleans += 2; // 0010
if(bool3) booleans += 4; // 0100
if(bool4) booleans += 8; // 1000
...
但是,这种方法在处理大量布尔值时很快就会变得低效,并且高度依赖于目标数组的大小。例如,如果您有一个大小为 16 的目标数组,则只有前 4 个对哈希值有影响(因为最大索引为 1111
)。
对此的两个解决方案是增加目标数组的大小(这可能不受您的控制),或者确保布尔值的顺序从可变参数最多到最少。这些都不是最佳的,因此这种方法既快速又简单,但在实践中不是很有效。
选项 2:改变碱基的哈希值
Pham Trung 在他的答案中展示的设计 扩展了选项 1,作为容纳多个字段的更简单的方法。正如Adrian Shum 评论的那样
,
基本思想是将每种类型的简化哈希值乘以某个任意大的素数,以确保每个哈希值都是唯一的(尽管我无法证明这一点)。例如:
int result = 0;
result = 31*result + bool1 ? 1 : 0;
result = 31*result + bool2 ? 1 : 0;
...
对于更稀疏的哈希分布,您可以将其与
Boolean.hashCode
结合使用,如其他答案所示:int result = 0;
result += 31*result + bool1.hashCode();
result += 31*result + bool2.hashCode();
...
此解决方案的优点在于它可以应用于其他类型,就像您在示例代码中已经拥有的那样:
...
result = 31*result + i;
result = 31*result + (a != null ? a.hashCode() : 0);
result = 31*result + my_complex_object.hashCode();
注意:在这些例子中,
31
37
、113
或 23456789
。然而,使用更大的被乘数是有代价的,即你的散列将更快地超过
Integer.MAX_VALUE
并使你的散列无效。
当你有两个甚至更多布尔值时,哈希码算法已经处理好了。
再仔细看一下:
// Very simple example
public int hashCode() {
int result = 31;
for(boolean val : booleanList) {
// This is the important part:
result = 31 * result + Boolean.hashCode(val);
}
return result;
}
注意
for循环的主要部分,在这种情况下,我们以不同的方式对待每个布尔值,因为我们总是将结果乘以31(或任何好的质数),然后再将其添加到结果中。如果我们将整个哈希码可视化为一些
base 31的数字,那么我们可以理解,每个布尔值的位置和值都被考虑到了最终结果中。每个布尔值都可以被视为哈希码中的数字,因此对于 case (true, false) 和 case (false, true),它们将具有两个不同的哈希码。
组合多个字段时,一个好的做法是从第一个字段的 hashCode 开始,然后将当前结果乘以素数,然后再添加每个字段的 hashcode:
@Override public int hashCode() {
int result = 0;
result = b1.hashCode();
result = result * 37 + b2.hashCode();
result = result * 37 + b3.hashCode();
// ...
// result = result * 37 + bn.hashCode();
return result;
}