【问题标题】:How does overriding equals and hashCode impacts storing data in a Map in Java with a bad hashing function?覆盖 equals 和 hashCode 如何影响在 Java 中使用错误的散列函数将数据存储在 Map 中?
【发布时间】:2017-01-25 05:09:23
【问题描述】:

我有一个类Employee,比方说,我的这个类的hashCode 函数真的很糟糕(假设它总是返回一个常量)。我的代码如下所示。

public class Employee {
 private String name;

 public Employee(String name) {
  this.name = name;
 }

 @Override
 public int hashCode() { return 1; }

 @Override
 public boolean equals(Object object) {
  if(null == object || !(object instanceof Employee)) {
   return false;
  }
  Employee other = (Employee)object;
  return this.name.equals(other.name);
 }
}

假设我想使用Employee 作为Map 中的键,所以我可以执行以下操作。

public static void main(String[] args) {
 Map<Employee, Long> map = new HashMap<>();
 for(int i=0; i < 1000; i++) {
  map.put(new Employee("john"+i, 1L));
 }
 System.out.println(map.size());
}

为什么当我运行这段代码时,我总是得到 1,000 作为大小?

使用Employee 作为键似乎在以下意义上是“好”的。

  • 它是不可变的
  • equals 的两个员工始终生成相同的哈希码

我的预期是因为hashCode 的输出总是1,那么map.size() 应该总是1。但事实并非如此。为什么?如果我有一个Map&lt;Integer,Integer&gt;,然后是map.put(1, 1),然后是map.put(1, 2),我只希望大小为1。

equals 方法一定会在这里发挥作用,但我不确定如何。

任何指针表示赞赏。

【问题讨论】:

    标签: java collections hashmap hashcode equality


    【解决方案1】:

    关于造成这种情况的原因,Mike 的回答是正确的。但它发生的真正原因是:

    在 HashMap 的 put 方法中,它首先检查每个条目的哈希码。如果哈希码等于新密钥的哈希码,那么它会检查 .equals()。如果 equals() 返回 true 它只是用新对象替换现有对象,否则添加一个新的键值对。这就是它被破坏的地方。因为有些东西你的 equals() 函数会因为 currentMilliSeconds 而返回 true,有时它不会因此每次都有不同的大小。

    注意下面代码中的等号(java HashMap)。

    public V put(K key, V value) {
        if (key == null)
            return putForNullKey(value);
        int hash = hash(key.hashCode());
        int i = indexFor(hash, table.length);
        for (Entry<K,V> e = table[i]; e != null; e = e.next) {
            Object k;
            if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
                V oldValue = e.value;
                e.value = value;
                e.recordAccess(this);
                return oldValue;
            }
        }
    
        modCount++;
        addEntry(hash, key, value, i);
        return null;
    }
    

    【讨论】:

      【解决方案2】:

      您的hashcode 必须符合某些要求,例如相等的对象应该返回相等的hashcode。 但是您的实现并不可靠,那么它会带来性能问题,如果您的许多对象具有相同的hashcode,您的查找只是变成 O(N) 而不是 O(1)。在您的情况下,它只是将所有项目放在List 中。所以大小是1000

      【讨论】:

        【解决方案3】:

        我认为您误解了 HashBuckets 中的 HashMap 的用途。 当您将两个不相等但具有相同hashCodeObjects 放入HashMap 时,这两个元素都将出现在同一Hashmap 中的HashBucket 中。仅当 HashMap 中存在与现有元素具有相同 hashCode 且为 equals 的元素时,才会覆盖元素。

        HashBuckets 使HashMap 的查找速度更快,因为在搜索元素时,只需要考虑HahsBucket 中与hashCode 对应的元素。这就是为什么写一个恒定的HashFunction 通常是一个坏主意。

        【讨论】:

          【解决方案4】:

          如果您的哈希码对于每个条目都相同,那么您的时间复杂度将为 O(n),因为哈希码会创建存储桶来存储您的元素。如果您只创建一个存储桶,那么您必须遍历整个存储桶才能获取您的元素。

          但是,如果您的哈希码对于每个元素都是唯一的,那么您将拥有一个唯一的存储桶,并且只需要遍历单个元素。

          桶查找(哈希)是 O(1),因此哈希码越好,时间复杂度就越高。

          【讨论】:

            【解决方案5】:

            你的循环

            for(int i=0; i < 1000; i++) {
                map.put(new Employee("john"+System.currentTimeMillis(), 1L));
            }
            

            在几毫秒内执行,因此System.currentTimeMillis() 将在循环的绝大多数迭代中返回相同的值。因此,您的数百个 john 将具有完全相同的名称 + 编号。

            然后,我们有 java 的延迟 Map,它没有 add() 方法,(如果项目已经存在,人们会合理地期望抛出异常,)但是它只有一个 put() 方法将添加或替换项目,而不会失败。因此,您的大多数 john 都会被后续的 john 覆盖,而不会增加地图大小,并且不会抛出任何异常来提示您做错了什么。

            此外,您似乎有点困惑,因为错误的 hashCode() 函数在地图上的影响究竟是什么。一个不好的hashCode() 只会导致冲突。哈希图中的碰撞不会导致项目丢失;它们只会导致地图的内部结构效率不高。本质上,常量hashCode() 将导致退化的映射在内部看起来像一个链表。插入和删除都将是低效的,但不会因此丢失任何项目。

            由于equals() 方法错误,或由于使用较新的项目覆盖它们,项目将丢失。 (您的代码就是这种情况。)

            【讨论】:

            • @ByeBye 是在equals方法中取的
            • @ByeBye 阅读问题。
            • 是的,你是对的。我将稍微修改一下我的问题以避免这个问题。
            • 你在中间那段让我失望了。实施没有任何阻碍。它的行为与记录的完全相同。有一个containsKey 方法可以确定是否会覆盖某些内容。
            • 我稍微修改了代码以避免这个问题。但现在我得到 1,000 作为地图的大小。哈希码不应该一直将值映射到同一个桶吗?
            猜你喜欢
            • 1970-01-01
            • 2011-04-24
            • 2020-06-09
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多