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

时间:2015-03-20 00:22:24

标签: java java-ee hashcode java-ee-7 hash-code-uniqueness

我有一个类的哈希码实现,哈希码实现与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;
}

我遇到了一个我正在使用非常大的数据集进行测试的场景,而我的集合没有这个类的预期数量的对象。仔细观察下面两个数据集导致相同的哈希码: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,这大于为上述数据集计算的哈希码= 50268236873,所以有溢出。使用long作为hashCode值的类型有什么后果吗?

感谢
Nohsib

编辑:

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

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

@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来确定两个对象是否相等。修正了equals方法实现解决了我的问题,hashset正在替换exisintg记录。

5 个答案:

答案 0 :(得分:9)

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

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

另请注意,虽然哈希码的合同规定任何两个对equals()响应为true的对象应具有相同的哈希码,但相反的情况不一定如此。

答案 1 :(得分:3)

以下是Java 8文档中的contract for hashCode(摘要):

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

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

  3. 以下是满足上述要求的最小定义:

    public int hashCode() {
      return 0;
    }
    

    java.util.*HashTable之类的所有HashMap集合都符合此合同,并且永远不会因重复的hashCodes而删除项目,即使在过度重复时也是如此上面的例子。它会很慢,但它会是正确的。

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

    • 重用/修改对象,使其哈希码在运行时更改(违反#1)
    • 未覆盖.equals(Object)
    • 使用错误的集合(在java.*之外),假设比合同指定的更多hashCode

答案 2 :(得分:0)

不需要hashCode是唯一的,只有当两个对象相等时,它们的hashesh也必须相等。

哈希碰撞是预期的并且是不可避免的,因为你注意到只有2 * maxint可能值,所以如果可能的对象空间超过这个数,就必然会发生碰撞。

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

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

答案 3 :(得分:0)

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

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

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

有关哈希映射实现的简要说明,请参阅this答案。

答案 4 :(得分:0)

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