![](/img/trans.png)
[英]How does Java 8's HashMap degenerate to balanced trees when many keys have the same hash code?
[英]Why is there no collision in a HashMap when different keys have the same hash code
我试图故意制造碰撞。
fun main(args: Array<String>) {
val india = Country("India1", 1000)
val india2 = Country("India2", 1000)
val countryCapitalMap: HashMap<Country, String> = hashMapOf()
countryCapitalMap.put(india, "Delhi1")
countryCapitalMap.put(india2, "Delhi2")
}
class Country(var name: String, var population: Long) {
override fun hashCode(): Int {
return if (name.length % 2 == 0) 31 else 95
}
override fun equals(obj: Any?): Boolean {
val other = obj as Country?
return if (name.equals(other!!.name, ignoreCase = true)) true else false
}
}
所以,我有india
和india2
对象。 我已经覆盖了 Country 的equals()
和hashCode()
方法,以便:
india.hashCode() == india2.hashCode()
--> trueindia.equals(india2)
--> false 根据Java HashMap 中的冲突解决方案和文章“让我们将这个 Country 对象放入 hashmap”的部分内容,如果 key1 的hash(key.hashCode())
的结果等于对 key2 的相同操作,则应该存在冲突。
所以,我设置断点来查看countryCapitalMap
的内容,发现它的size
是 2。即它包含两个不同的条目,并且没有linkedList。 因此,不存在碰撞。
我的问题是:
为什么countryCapitalMap
的大小为2
? 为什么没有碰撞?
为什么HashMap
不创建一个包含两个条目的LinkedList
,其键不相等但具有相同的 hashCode?
您混淆了冲突- 当键的哈希值相同时(或者更准确地说,当哈希值在与HashMap
的同一桶对应的范围内时),具有重复键(即根据equals()
方法)。 这是两种完全不同的情况。
在底层, HashMap
维护一个buckets数组。 每个桶对应一个哈希值范围。
如果键的哈希冲突(但键不相等)条目(准确地说是Node
)将映射到同一个桶并形成一个链表,经过一定的阈值后会变成一棵树。
相反,尝试使用映射中已经存在的键(即,根据equals()
方法的重复键)添加新条目将导致更新现有条目的值。
因为正如您已经观察到的那样, india.equals(india2)
将返回false
, HashMap
将包含两个条目。
而且由于india
和india2
的哈希值是相同的,因此您已经成功地实现了创建冲突的意图。 两个条目都将被添加,最终都将在同一个存储桶中(即它们的节点将形成一个链表)。
如果您想知道这个链表到底长什么样,请查看HashMap
类的源代码。 你会发现有一个字段Node<K,V>[] table
- 一个buckets数组,还有一个内部类Node
:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
并且每个节点都有一个引用next
指向映射到同一个桶的下一个节点(如果有的话)。
旁注:
equals/hashCode
合约的正确实现对于打算与 Collections 框架一起使用的每个对象都至关重要。 对于像HashMap
和HashSet
这样的基于散列的集合, hashCode()
的实现对于良好的执行非常重要。 如果散列函数产生大量冲突,结果是许多条目可能出现在同一个桶中,同时大量桶可能保持空置。 并且根据负载因子(占用的桶数和桶总数之间的比率),您的集合可能永远不会调整大小,这将导致性能下降。hashCode()
只会被调用一次,同时会创建一个新节点。 看看上面的代码,你会注意到字段hash
被标记为final
。 这是故意这样做的,因为计算哈希某些对象可能会很昂贵。 这意味着如果这个键的状态发生变化, HashMap
将不知道该变化并且将无法识别相同的键,即如果前一个和新的散列不同,则不会调用equals()
方法并且HashMap
将允许相同的键出现两次。 这导致了另一个经验法则:用作键的对象应该是不可变的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.