覆盖 equals 和 hashCode 如何影响在 Java 的 Map 中使用错误的散列函数存储数据?

How does overriding equals and hashCode impacts storing data in a Map in Java with a bad hashing function?

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

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作为key在下面的意义上似乎是"good"。

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

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

感谢任何指点。

你的循环

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

在几毫秒内执行,因此 System.currentTimeMillis() 将为循环的绝大多数迭代返回相同的值。因此,您的数百个约翰将拥有完全相同的名字和号码。

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

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

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

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

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

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

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

HashBuckets使得HashMap查找速度快,因为在查找元素时,只需要考虑hashCode对应的HahsBucket中的元素.这就是为什么写一个常量的 HashFunction 通常不是一个好主意。

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

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

注意下面代码中的equals (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;
}

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