进入“ on / off主题”的灰色区域,但是有必要消除关于Oscar Reyes的困惑,因为更多的哈希冲突是一件好事,因为它减少了HashMap中的元素数量。我可能会误解Oscar在说什么,但我似乎并不是唯一一个:kdgregory,delfuego,Nash0,而且我似乎都拥有相同的(误解)理解。
如果我理解Oscar对于具有相同哈希码的相同类的说法,他建议仅将具有给定哈希码的类的一个实例插入到HashMap中。例如,如果我有一个哈希码为1的SomeClass实例和另一个哈希码为1的SomeClass实例,则仅插入一个SomeClass实例。
http://pastebin.com/f20af40b9上的Java pastebin示例似乎表明上述内容正确地总结了Oscar的建议。
无论有什么理解或误解,发生的事情是,如果相同类的不同实例具有相同的哈希码,则它们不会仅一次插入到HashMap中-直到确定键是否相等为止。哈希码协定要求相等的对象具有相同的哈希码;但是,不要求不相等的对象具有不同的哈希码(尽管出于其他原因这可能是理想的)[1]。
随后是pastebin.com/f20af40b9示例(Oscar至少引用了两次),但对其进行了少许修改以使用JUnit断言而不是打印行。此示例用于支持以下建议:相同的哈希码会导致冲突,并且当类相同时,仅创建一个条目(例如,在此特定情况下,仅创建一个String):
@Test
public void shouldOverwriteWhenEqualAndHashcodeSame() {
String s = new String("ese");
String ese = new String("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// AND equal
assertTrue(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(2, map.size());
assertEquals(2, map.get("ese"));
assertEquals(3, map.get(some));
assertTrue(s.equals(ese) && s.equals("ese"));
}
class SomeClass {
public int hashCode() {
return 100727;
}
}
但是,哈希码并不是完整的故事。pastebin示例忽略了一个事实,即两者s
和ese
都相等:它们都是字符串“ ese”。因此,使用s
或ese
或"ese"
作为键插入或获取地图的内容都是等效的,因为s.equals(ese) && s.equals("ese")
。
第二个测试表明,得出结论,认为同一类上的相同哈希码是键->值s -> 1
被测试一中的ese -> 2
when 覆盖的原因是错误的map.put(ese, 2)
。在试验二,s
并且ese
仍然有相同的哈希码(通过验证assertEquals(s.hashCode(), ese.hashCode());
),他们是同一类。但是,s
并且ese
是MyString
该测试中的实例,而不是Java String
实例-与该测试相关的唯一区别是等于:String s equals String ese
在上面的测试一中,而MyStrings s does not equal MyString ese
在测试二中:
@Test
public void shouldInsertWhenNotEqualAndHashcodeSame() {
MyString s = new MyString("ese");
MyString ese = new MyString("ese");
// same hash right?
assertEquals(s.hashCode(), ese.hashCode());
// same class
assertEquals(s.getClass(), ese.getClass());
// BUT not equal
assertFalse(s.equals(ese));
Map map = new HashMap();
map.put(s, 1);
map.put(ese, 2);
SomeClass some = new SomeClass();
// still same hash right?
assertEquals(s.hashCode(), ese.hashCode());
assertEquals(s.hashCode(), some.hashCode());
map.put(some, 3);
// what would we get?
assertEquals(3, map.size());
assertEquals(1, map.get(s));
assertEquals(2, map.get(ese));
assertEquals(3, map.get(some));
}
/**
* NOTE: equals is not overridden so the default implementation is used
* which means objects are only equal if they're the same instance, whereas
* the actual Java String class compares the value of its contents.
*/
class MyString {
String i;
MyString(String i) {
this.i = i;
}
@Override
public int hashCode() {
return 100727;
}
}
根据后来的评论,奥斯卡似乎改变了他先前所说的话,并承认平等的重要性。但是,似乎仍然很重要的概念是不清楚,而不是“同一阶级”(强调我的意思):
“不是真的。仅在哈希相同但键不同的情况下才创建列表。例如,如果String给出哈希码2345,而Integer给出相同的哈希码2345,则由于String,将整数插入列表。 equals(Integer)是false。但是,如果您具有相同的类(或至少.equals返回true),则使用相同的条目。例如,new String(“ one”)和`new String(“ one”)用作键,将使用相同的条目。实际上,这是HashMap的整个起点!亲自查看:pastebin.com/f20af40b9 – Oscar Reyes”
与先前的注释明确指出了相同的类和相同的哈希码的重要性,而没有提及equals:
“ @delfuego:自己看看:pastebin.com/f20af40b9因此,在这个问题中,使用了相同的类(请等待一分钟,使用相同的类对吗?),这意味着当使用相同的哈希时,应使用相同的条目被使用,并且没有条目“列表”。– Oscar Reyes”
要么
“实际上,这将提高性能。冲突越多,哈希表中的条目越少。要做的工作越少。不是我认为它在对象上的哈希(看起来不错)或哈希表(效果很好)表现令人失望的创作。–奥斯卡·雷耶斯(Oscar Reyes)
要么
“ @kdgregory:是的,但是仅当冲突发生在不同的类上时,对于相同的类(这种情况),将使用相同的条目。– Oscar Reyes”
再一次,我可能会误解奥斯卡实际上想说的话。但是,他的原始评论引起了足够的混乱,以至于通过一些明确的测试来清除所有内容似乎是谨慎的做法,因此没有挥之不去的疑问。
[1] -Joshua Bloch撰写的有效Java第二版:
只要在应用程序执行期间在同一个对象上多次调用它,则hashCode方法必须一致地返回相同的整数,前提是未修改在该对象的equals比较中使用的信息。从一个应用程序的执行到同一应用程序的另一执行,此整数不必保持一致。
如果根据相等的s(Obj ect)方法两个对象相等,则在两个对象中的每个对象上调用hashCode方法必须产生相同的整数结果。
如果两个对象根据相等的s(Object)方法不相等,则不需要在两个对象中的每个对象上调用hashCode方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。