如何确保 hashcode() 不会解析为 Java 中的相同值?

How to ensure hashcode() does not resolve to same value in Java?

我有一个 class 的哈希码实现,哈希码实现与 eclipse 生成的内容以及所讨论的最普遍接受的做法一致 here

这是我的哈希码实现(此方法中使用的所有 ID 构成对象的键):

public int hashCode() {
    final int prime = 31;
    int hashCode = 1;
    if(uId != null){
        hashCode = prime * hashCode + uId.hashCode();
    }
    if(rId != null){
        hashCode = prime * hashCode + rId.hashCode();
    }
    if(bId != null){
        hashCode = prime * hashCode + bId.hashCode();
    }
    if(reId != null){
        hashCode = prime * hashCode + reId.hashCode();
    }
    if(cId != null){
        hashCode = prime * hashCode + cId.hashCode();
    }
    return hashCode;
}

我 运行 进入了一个场景,在该场景中,我正在使用非常大的数据集进行测试,而我的集合没有此 class 的预期对象数。仔细观察以下两个数据集产生了相同的哈希码:50268236873,因此记录被添加到集合中的最后一个记录替换,因为它们的哈希码相同。

  Existing record :
  Record@2c0781cd[uId=54046,rId=10967,bId=177,reId=1728,cId=50194] 

  Record being inserted into the collection :
  Record@20dad050[uId=53806,rId=18389,bId=177,reId=19026,cId=50194]

Both of these had the hashCode value = 50268236873 

所以,问题:

1] 这是两个不同对象的哈希码具有相同值的明显情况。那么如何确保任何数据集都不会发生这种情况呢?质数是否应该更大?

2] 如果我们仔细观察实现中的 hashCode 变量是 int 数据类型,其最大值为 2^31 - 1 = 2147483647 大于为上述数据集计算的 hashcode = 50268236873,因此存在溢出.使用 long 作为 hashCode 值的类型有什么后果吗?

谢谢
诺西布

编辑:

我正在使用 HashSet,在阅读了发布的答案后,我查找了 equals 的实现,如下所示,我想因为在 equals 中我检查两个对象的 hashCode 是否相同并使用它确定它们是否是相同的对象会导致此问题。

你们中的任何人都可以证实这一点吗?

@Override
    public boolean equals(Object paramObject) {
        boolean equals = false;
        if (paramObject != null) {
            ACRecord other = (ACRecord) paramObject;
            if ((this.hashCode() == other.hashCode()) // I think this is where I am going wrong
                    || (this.uId.equals(other.getUId())
                            && this.rId.equals(other.getRId())
                            && this.reId.equals(other.getReId())
                            && this.bId.equals(other.getBId()) 
                            && this.cId.equals(other.getCId))) {
                equals = true;
            }
        }
        return equals;
    }

解决方案:我的 equals 方法实现是错误的,因为我使用 hashCode 来确定两个对象是否 equal.Correcting equals 方法实现解决了我的问题,因为 hashset 正在替换现有的记录。

通常,哈希码不保证唯一性。 HashMap 实现通常通过在幕后存储一个列表来处理冲突,但它们包含一个检查,以确保您不会将列表中的所有内容都作为匹配项,只是 真正比赛。

换句话说,如果您执行 map.get("foo") 并且存在冲突,哈希映射将检查每个结果(未哈希)以查看它是否真的匹配 "foo" .然后它 returns 只完全匹配。

另请注意,虽然哈希码契约规定任何两个响应 equals() 的对象都应具有相同的哈希码,但反之则不一定正确。

没有要求 hashCode 是唯一的,只是如果两个对象相等,它们的 hashash 也必须相等。

哈希冲突是意料之中的,也是不可避免的原因,正如您所注意到的,可能只有 2*maxint 个可能值,因此如果可能的对象 space 超过这个数字,则一定会发生冲突。

您不能将 hashCode 更改为 long,因为它已经被定义为 int 并且将被使用。

像hashMap 或HashSet 这样的集合知道可能的冲突并且它们不受它们的影响。您的自定义代码也必须是防碰撞的。

哈希码通常将大范围的值映射到较小的值范围。这意味着即使是最完美的数据散列算法也会在达到 n + 1 值时产生冲突,其中 n 是可能散列值的数量(当使用 int 作为散列码时会是 2^32 )

您的实现需要通过对对象的所有成员进行全面检查以验证它们实际上是否相等来处理此类冲突。

散列通常通过减少验证结果所需的检查量来大大减少完整检查,因为您只需要检查具有相同散列码的值,直到找到与您的数据完全匹配的值,或者如果none 匹配您的数据不在地图中。

请参阅 this 答案以获取哈希映射实现的简要说明。

这是 Java 8 个文档中的 contract for hashCode(摘要):

  1. 对同一个对象调用该方法两次必须产生相同的值(每个 JVM 实例)。

  2. 如果根据a.equals(b),两个对象ab相等,则hashCodes必须相同。

满足上述条件的最小定义如下:

public int hashCode() {
  return 0;
}

所有 java.util.* 集合,如 HashTableHashMap 都符合此契约,并且 永远不会 由于重复的 hashCode 而丢弃项目,即使当像上面的例子一样过度复制时。会慢点,但会正确。

相反,在基于散列的集合中添加或检索时出现意外结果的典型原因包括:

  • Reusing/modifying 对象的哈希码在运行时发生变化(违反#1)
  • 不覆盖 .equals(Object)
  • 使用有缺陷的集合(在 java.* 之外)假设更多关于 hashCode 而不是合同指定的内容。

哈希从来都不是完全唯一的。但是,有一些散列算法可以更好地避免冲突。正如您在代码中已有的那样,通常最好使用素数来帮助解决冲突。