繁体   English   中英

当不同的键具有相同的哈希码时,为什么在 HashMap 中没有冲突

[英]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
  }
}

所以,我有indiaindia2对象。 我已经覆盖了 Country 的equals()hashCode()方法,以便:

  • india.hashCode() == india2.hashCode() --> true
  • india.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)将返回falseHashMap将包含两个条目。

而且由于indiaindia2的哈希值是相同的,因此您已经成功地实现了创建冲突的意图。 两个条目都将被添加,最终都将在同一个存储桶中(即它们的节点将形成一个链表)。

如果您想知道这个链表到底长什么样,请查看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 框架一起使用的每个对象都至关重要。 对于像HashMapHashSet这样的基于散列的集合, hashCode()的实现对于良好的执行非常重要。 如果散列函数产生大量冲突,结果是许多条目可能出现在同一个中,同时大量可能保持空置。 并且根据负载因子占用的桶数和桶总数之间的比率),您的集合可能永远不会调整大小,这将导致性能下降。
  • 值得一提的与哈希相关的另一件事是密钥hashCode()只会被调用一次,同时会创建一个新节点 看看上面的代码,你会注意到字段hash被标记为final 这是故意这样做的,因为计算哈希某些对象可能会很昂贵。 这意味着如果这个的状态发生变化, HashMap将不知道该变化并且将无法识别相同的,即如果前一个和新的散列不同,则不会调用equals()方法并且HashMap将允许相同的键出现两次。 这导致了另一个经验法则:用作的对象应该是不可变的。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM