【问题标题】:HashMap yields same hashCode for different contentsHashMap 为不同的内容产生相同的 hashCode
【发布时间】:2014-10-28 01:07:59
【问题描述】:

我有以下单元测试:我创建了 2 个不同的自定义类型变量的对象。我比较了他们的哈希码,它只是返回他们名字的哈希码,即 String.hashCode()。

然后我创建 2 个 HashSet,每个都包含一个变量并比较集合的哈希码。

正如预期的那样,在这两种情况下,哈希码都不同。

但是,如果我创建一个名称为索引、变量为值的 HashMap,则断言失败,即它们比较相同。这是为什么呢?

使用 Oracle Java 1.8。

编辑:我可以添加另一个保证:Assert.assertNotEquals(map1, map2); 也成立。 此外,我认为我正确地解释了这句话:

地图的哈希码定义为地图的哈希码之和 地图的 entrySet() 视图中的每个条目。这确保了 m1.equals(m2) 意味着 m1.hashCode()==m2.hashCode() 对于任何两个 地图 m1 和 m2,根据总合同的要求 对象.hashCode()。 取自http://docs.oracle.com/javase/7/docs/api/java/util/AbstractMap.html#hashCode%28%29

@Test
public void test() {
    // this assertion holds
    Assert.assertNotEquals(new Variable("x").hashCode(), new Variable("y").hashCode());

    Set<Variable> set1 = new HashSet<>();
    set1.add(new Variable("x"));
    Set<Variable> set2 = new HashSet<>();
    set2.add(new Variable("y"));
    // this assertion also holds
    Assert.assertNotEquals(set1.hashCode(), set2.hashCode());

    HashMap<String, Variable> map1 = new HashMap<>();
    map1.put("x", new Variable("x"));
    HashMap<String, Variable> map2 = new HashMap<>();
    map2.put("y", new Variable("y"));
    // why does this assertion fail?
    Assert.assertNotEquals(map1.hashCode(), map2.hashCode());
}

这是类变量的定义。

public class Variable {
    private String name;

    public Variable(String name) {
        this.name = name;
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof Variable))
            return false;
        return name.equals(((Variable) obj).name);
    }
}

【问题讨论】:

  • 为什么你认为哈希码在任何情况下都必须不同?
  • 我希望第三个断言成立,因为第一个断言成立。第二个断言也成立的原因相同。
  • 您使用的Variable 的构造函数是否将传递的值分配给name
  • @PM77-1,是的,否则第一个断言会失败。

标签: java hash hashmap


【解决方案1】:

AbstractMap.EntryHashMap.Entry 的 Oracle 实现将 hashCode 定义为:

public int hashCode() {
    return (key   == null ? 0 :   key.hashCode()) ^
           (value == null ? 0 : value.hashCode());
}

注意 XOR 运算符。如果键和值都具有相同的哈希码,它们将在异或时抵消,并且该条目的整体哈希码将为 0。

这发生在您的代码中,因为 Variable 的哈希码与您传递给它的字符串相同,并且这些字符串与键相同。

值得注意的是,不保证不同的对象具有不同的哈希码。哈希码的唯一保证是相等的对象将具有相等的哈希码。如果散列函数好,不相等的对象通常会有不同的散列码,但这不是保证。

事实证明,这不仅仅是理论上的可能性。在真实的程序中是很有可能的!

接下来的逻辑问题是:我怎样才能避免这种情况? HashMap 是一种我非常喜欢按名称索引的符号表。而且 atm 我没有更多有用的类变量成员可以包含在 equals() 和 hashCode() 中。有什么想法吗?

您可以为 Variable 提供与内置 String 实现不同的哈希码。一个简单的方法是使用 stock 实现,但 change the multiplier from 31 使用其他素数。

例如:

private int hash;

@Override public int hashCode() {
    int h = hash;
    if (h == 0) {
        int len = name.length();
        h = 1;
        for (int i = 0; i < len; i++) {
            h = 47*h + name.charAt(i);
        }
        hash = h;
    }
    return h;
}

这是 OpenJDK 的 String.hashCode() 实现的修改版本。我添加了h = 1,所以即使是 1 个字符的字符串也会有所不同。

【讨论】:

  • 我同意这一点。但是元素成对不同,hashmap 的 hashcode 被定义为其元素的 hashcode 之和,不是吗?另请参阅我对我发布 map1.equals(map2) 不正确的原始问题的编辑。
  • 我明白了。我从没想过这会成为现实世界中的问题。我刚刚将一个变量的名称更改为“asdasdfasdfasdf”,现在断言按预期成立。不知道该说什么……我必须重新设计我的程序的这方面。
  • 好的,接下来的逻辑问题是:我怎样才能避免这种情况? HashMap 是一种我非常喜欢按名称索引的符号表。而且 atm 我没有更多有用的类变量成员可以包含在 equals() 和 hashCode() 中。有什么想法吗?
  • 感谢您的建议。我会用那个。
【解决方案2】:

这只是巧合。 hashCode()HashMap.Node 实现(HashMap#hashCode() 使用)是

public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
}

其中keyvalue 具有相同的hashCode。例如,key"x"value 是使用 name"x"(它用于其 hashCode)创建的 Variable 对象。换句话说,"x".hashCode()new Variable("x").hashCode() 是相等的。

任何value ^ value 都等于0。因此,您的地图的hashCode 对于两个地图都是0。

【讨论】:

  • 你们同时给出了解释。不能接受你们俩,对不起。
  • @hooch 无需道歉。只要你掌握了概念。我只是想在代码方面进行布局。
  • 是的,我正在调试代码,但我懒得看不是我自己写的代码。现在我看到了,很明显为什么会发生这种情况。再次感谢。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-05-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-09-25
相关资源
最近更新 更多