覆盖 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"。
- 它是不可变的
equals
的两名员工总是生成相同的哈希码
我期望的是,既然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
的用途。
当您将两个不相等但具有相同 hashCode
的 Objects
放入 HashMap
中时,两个元素将出现在相同 HashBucket
中的 Hashmap
中.仅当 HashMap
中存在与现有元素具有相同 hashCode
且 equals
的元素时,才会覆盖该元素。
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
.
我有一个 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"。
- 它是不可变的
equals
的两名员工总是生成相同的哈希码
我期望的是,既然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
的用途。
当您将两个不相等但具有相同 hashCode
的 Objects
放入 HashMap
中时,两个元素将出现在相同 HashBucket
中的 Hashmap
中.仅当 HashMap
中存在与现有元素具有相同 hashCode
且 equals
的元素时,才会覆盖该元素。
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
.