进入“开/关主题”的灰色区域,但有必要消除关于 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 断言而不是 printlines。此示例用于支持相同哈希码导致冲突的提议,并且当类相同时仅创建一个条目(例如,在此特定情况下仅创建一个字符串):
@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")。
第二个测试表明,当在测试一个中调用map.put(ese, 2) 时,得出相同类上的相同哈希码是键-> 值s -> 1 被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;
}
}
根据后来的评论,奥斯卡似乎颠倒了他之前所说的并承认平等的重要性。但是,似乎重要的是等于,而不是“同一类”的概念仍然不清楚(强调我的):
"不是真的。仅当哈希相同但键不同时才会创建列表。例如,如果字符串给出哈希码 2345,而整数给出相同的哈希码 2345,则插入整数进入列表,因为 String.equals( Integer ) 为 false。但是 如果您有相同的类(或至少 .equals 返回 true ),则使用相同的条目。例如 new String("one ") 和 `new String("one") 用作键,将使用相同的条目。实际上这是 HashMap 的全部要点!您自己看看:pastebin.com/f20af40b9 – Oscar Reyes"
与较早的 cmet 相比,它们明确解决了相同类和相同哈希码的重要性,但没有提及 equals:
"@delfuego:自己看看:pastebin.com/f20af40b9 所以,在这个问题中,使用的是同一个类(等一下,使用的是同一个类对吗?)这意味着当相同哈希使用相同的条目,并且没有条目的“列表”。- Oscar Reyes"
或
“实际上这会提高性能。冲突越多,哈希表 eq 中的条目越少。要做的工作就越少。不是哈希(看起来不错)也不是哈希表(效果很好)我会打赌它是在性能下降的对象创建上。– Oscar Reyes"
或
“@kdgregory:是的,但只有当碰撞发生在不同的类时,对于同一个类(就是这种情况),使用相同的条目。- Oscar Reyes”
再一次,我可能误解了奥斯卡实际上想说什么。然而,他最初的 cmets 已经引起了足够多的混乱,似乎谨慎的做法是通过一些明确的测试来澄清一切,这样就没有挥之不去的疑问。
[1] - 来自 Joshua Bloch 的 Effective Java, Second Edition:
在应用程序执行期间,只要在同一个对象上多次调用它,hashCode 方法必须始终返回
相同的整数,提供没有在相等的比较中使用的信息
对象被修改。该整数不需要在应用程序的一次执行与同一应用程序的另一次执行之间保持一致。
如果两个对象根据 equal s(Obj ect) 方法相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同
整数结果。
不要求如果两个对象根据equal s(Object)方法不相等,则对两个对象分别调用hashCode方法
必须产生不同的整数结果。但是,程序员应该
意识到为不相等的对象产生不同的整数结果可能会提高
哈希表的性能。