在Hashmap中,提供的密钥的哈希码用于将值放在哈希表中。在Hashset中,obects哈希码用于将值放在底层哈希表中。即hashmap的优点是你可以灵活地决定你想要什么作为键,这样你就可以做到这样的好事。
Map<String,Player> players = new HashMap<String,Player>();
这可以将玩家名称等字符串映射到玩家本身。
我的问题是当密钥的Hashcode发生变化时,查找会发生什么。
我希望这不是Hashmap的主要问题,因为我不希望也不希望密钥发生变化。在前面的例子中,如果球员名称发生变化,他就不再是那个球员了。但是,我可以使用密钥更改其他不是名称的字段来查看播放器,将来的查找将起作用。
然而在Hashset中,由于整个对象的哈希码用于放置项目,如果某人略微更改了对象,则该对象的未来查找将不再解析为Hashtable中的相同位置,因为它依赖于整个对象Hashcode。这是否意味着一旦数据在Hashset中就不应该被更改。还是需要重新加入?还是自动完成等?发生了什么事?
答案 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元素,则必须非常小心。如果在对象是集合中的元素的同时以影响等于比较的方式更改对象的值,则不指定集合的行为。这种禁令的一个特例是,不允许集合将自身作为一个要素包含在内。
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)
HashSet
由HashMap
备份。
来自 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
方法找不到的任何键。最好从地图中删除密钥,然后用新密钥存储该值。