当对象 Hashcode 更改时,Hashmap 或 Hashset 中的查找会发生什么情况

2022-09-03 01:22:56

在哈希映射中,所提供密钥的哈希代码用于将值放在哈希表中。在哈希集中,obects 哈希码用于将值放在基础哈希表中。即,哈希映射的优点是您可以灵活地决定您想要什么作为密钥,因此您可以做这样的好事。

Map<String,Player> players = new HashMap<String,Player>();

这可以将字符串(如玩家名称)映射到玩家本身。

我的问题是,当密钥的哈希码更改时,查找会发生什么情况。

我预计这对于Hashmap来说并不是一个主要问题,因为我不希望也不希望密钥发生变化。在前面的示例中,如果玩家姓名更改,他不再是该玩家。但是,我可以使用键更改来查找玩家,其他字段不是名称,并且将来的查找将起作用。

但是,在哈希集中,由于整个对象的哈希码用于放置项目,如果有人稍微更改了对象,则该对象的未来查找将不再解析到哈希表中的相同位置,因为它依赖于整个对象哈希码。这是否意味着一旦数据位于哈希集中,就不应该更改它。还是需要重新讨论?还是自动完成等?这是怎么回事?


答案 1

在您的示例中,String 是不可变的,因此其哈希代码无法更改。但假设,如果一个对象的哈希码在成为哈希表中的键时确实发生了变化,那么就哈希表查找而言,它可能会消失。我在回答一个相关问题时更详细地介绍了这个问题:https://stackoverflow.com/a/13114376/139985。(最初的问题是关于 a ,但 a 实际上是一个幕后花絮,所以答案也涵盖了这种情况。HashSetHashSetHashMap

可以肯定地说,如果HashMap或TreeMap的键以影响其各自/或合约的方式发生突变,那么数据结构将“中断”。hashcode()equals(Object)compare(...)compareTo(...)


这是否意味着一旦数据位于哈希集中,就不应该更改它。

是的。

还是需要重新讨论?还是自动完成等?

它不会自动重新哈希。不会注意到密钥的哈希码已更改。实际上,在调整大小时,您甚至不会重新计算哈希码。数据结构会记住原始哈希码值,以避免在哈希表调整大小时必须重新计算所有哈希码。HashMapHashMap

如果您知道密钥的哈希码将发生变化,则需要在更改密钥之前从表中删除该条目,然后在之后将其添加回去。(如果您在更改密钥后尝试/it,则很可能将无法找到该条目。removeputremove

这是怎么回事?

发生的事情是你违反了合同。别这样!

合同由两部分组成:

  1. 标准哈希码 / 等于 javadoc 中指定的协定。Object

  2. 一个附加约束,即对象的哈希码在成为哈希表中的键时不得更改。

后一个约束在javadoc中没有特别说明,但javadoc是这样的:HashMapMap

注意:如果将可变对象用作映射键,则必须格外小心。如果对象的值的更改方式会影响相等比较,而对象是映射中的键,则不会指定映射的行为。

影响相等性的更改(通常)也会影响哈希码。在实现级别,如果条目的键的哈希码发生更改,则该条目现在通常位于错误的哈希存储桶中,并且对于执行查找的方法不可见。HashMapHashMap


答案 2

在您的示例中,键是不可变的字符串。因此,密钥的哈希码不会更改。当密钥的哈希码更改未定义并导致“奇怪”行为时会发生什么。请参阅下面的示例,其中打印了 1、false 和 2。对象仍保留在集合中,但该集合看起来已损坏(包含返回 false)。

摘自 Set 的 javadoc

注意:如果将可变对象用作集合元素,则必须格外小心。如果对象的值的更改方式会影响相等比较,而对象是集合中的元素,则不会指定集合的行为。此禁止的一个特殊情况是,不允许集合将自身作为元素包含。

public static void main(String args[]) {
    Set<MyObject> set = new HashSet<>();
    MyObject o1 = new MyObject(1);
    set.add(o1);
    o1.i = 2;
    System.out.println(set.size());       //1
    System.out.println(set.contains(o1)); //false
    for (MyObject o : set) {
        System.out.println(o.i);          //2
    }
}

private static class MyObject {
    private int i;

    public MyObject(int i) {
        this.i = i;
    }

    @Override
    public int hashCode() {
        return i;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) return false;
        if (getClass() != obj.getClass()) return false;
        final MyObject other = (MyObject) obj;
        if (this.i != other.i) return false;
        return true;
    }
}