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

时间:2012-11-01 12:30:39

标签: java hash hashmap hashcode hashset

在Hashmap中,提供的密钥的哈希码用于将值放在哈希表中。在Hashset中,obects哈希码用于将值放在底层哈希表中。即hashmap的优点是你可以灵活地决定你想要什么作为键,这样你就可以做到这样的好事。

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

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

我的问题是当密钥的Hashcode发生变化时,查找会发生什么。

我希望这不是Hashmap的主要问题,因为我不希望也不希望密钥发生变化。在前面的例子中,如果球员名称发生变化,他就不再是那个球员了。但是,我可以使用密钥更改其他不是名称的字段来查看播放器,将来的查找将起作用。

然而在Hashset中,由于整个对象的哈希码用于放置项目,如果某人略微更改了对象,则该对象的未来查找将不再解析为Hashtable中的相同位置,因为它依赖于整个对象Hashcode。这是否意味着一旦数据在Hashset中就不应该被更改。还是需要重新加入?还是自动完成等?发生了什么事?

4 个答案:

答案 0 :(得分:17)

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

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


  

这是否意味着一旦数据在Hashset中就不应该被更改。

  

还是需要重新加入?还是自动完成等?

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

如果您知道密钥的哈希码将要更改,则需要在更改密钥之前从表中删除该条目,然后将其添加回来。 (如果您在更改密钥后尝试HashMap / remove,则put可能无法找到该条目。)

  

发生了什么事?

发生的事情是你违反了remove javadocs中明确规定的合同。不要那样做!

答案 1 :(得分:2)

在您的示例中,键是String,它们是不可变的。因此密钥的哈希码不会改变。当密钥的哈希码发生变化时,会发生什么,并且会导致“怪异”的行为。请参阅下面的示例,其中打印1,false和2.对象保留在集合中,但该集合看起来已损坏(包含返回false)。

摘自Set's javadoc

  

注意:如果将可变对象用作set元素,则必须非常小心。如果在对象是集合中的元素的同时以影响等于比较的方式更改对象的值,则不指定集合的​​行为。这种禁令的一个特例是,不允许集合将自身作为一个要素包含在内。

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;
    }
}

答案 2 :(得分:0)

HashSetHashMap备份。

来自 javadocs

  

此类实现Set接口,由哈希表支持   (实际上是一个HashMap实例)。

因此,如果您更改哈希码,我怀疑您是否可以访问该对象。

内部实施细则

add HashSet的实施是

 public boolean add(E e) {
        return map.put(e, PRESENT)==null;
 }

键是elem,value只是一个名为PRESENT

的虚拟对象

并且contains实施

public boolean contains(Object o) {
        return map.containsKey(o);
}

答案 3 :(得分:0)

使用Java的哈希,根本找不到原始引用。它在与当前哈希码相对应的存储桶中搜索,但未找到。

要在事后恢复,必须迭代Hash keySet,并且必须通过迭代器删除contains方法找不到的任何键。最好从地图中删除密钥,然后用新密钥存储该值。