虽然哈希值不同,但为什么我的对象存储在同一个位置?
Although hash values are different, still why are my objects stored in the same location?
我有一个 Movie
class 并且 我只覆盖 hashCode()
方法 。请在下面找到 java class
public class Movie {
private String actor;
private String name;
private String releaseYr;
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getReleaseYr() {
return releaseYr;
}
public void setReleaseYr(String releaseYr) {
this.releaseYr = releaseYr;
}
@Override
public int hashCode() {
return actor.hashCode() + name.hashCode() + releaseYr.hashCode();
}
}
我创建了两个 Movie
对象,两个对象的所有 属性 值都相同,并将它们放在 HashMap
中。下面是代码
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
Movie m1 = new Movie();
m1.setActor("Akshay");
m1.setName("Taskvir");
m1.setReleaseYr("2010");
Movie m2 = new Movie();
m2.setActor("Akshay");
m2.setName("Taskvir");
m2.setReleaseYr("2010");
HashMap<Movie, String> map = new HashMap<Movie, String>();
map.put(m1, "Value of m1");
map.put(m2, "Value of m2");
}
}
我得到了预期的结果。由于我只覆盖了 hashCode() 方法,而且对象的 哈希值相同,所以它们存储在 HashMap table 数组的相同索引位置 中。下面是调试模式下的预期结果。
但是如果我不覆盖 hashCode() 方法而是覆盖 equals() 方法,它们将存储在 HashMap table 数组的相同索引位置。 尽管我可以看到哈希值不同。下面是我的 equals 方法
@Override
public boolean equals(Object obj) {
Movie m1 = (Movie) obj;
boolean result = false;
if (m1.getActor().equals(this.actor) && m1.getName().equals(this.name)
&& m1.getReleaseYr().equals(this.releaseYr)) {
result = true;
}
return result;
}
调试模式下的输出
如果我不覆盖 equals 和 hashCode 方法,那么我也会得到同样的意外结果。
根据我的理解,如果我不覆盖 equals
和 hashCode
方法或只覆盖 equals
方法,那么 m1
和 m2
对象应该存储在不同的位置,因为 m1
和 m2
对象的哈希值不同。但在这种情况下,它没有发生。
有人可以解释一下为什么我的对象存储在相同位置的哈希值不同吗?
我用过Java8.
哈希码有一个 巨大的 范围,从 Integer.MIN_VALUE
到 Integer.MAX_VALUE
,而 HashMap
通常有更少的桶(默认情况下, 16 对于新实例化的 HashMap
,至少对于 OpenJDK 11)。因此,哈希码发生冲突是完全有可能的,甚至是意料之中的,并且多个对象将被添加到同一个桶中。但是,请注意,如果您没有覆盖 hashCode()
,此行为完全是偶然的,不能依赖。
无论哈希代码是如何计算的,通过您的方法或默认来自 Object
class,不同的对象都可以映射到相同的哈希映射桶(数组索引)。哈希码除以数组大小,余数给出桶号
Object.hashCode()
生成的两个哈希码(31622540 和 27844196)在除以 16(初始 HashMap 数组大小)时恰好生成相同的余数 4。
因此,有 40 亿个不同的哈希码可用,其中一些必须在同一个桶中结束,因为为每个哈希映射分配一个 40 亿个元素的数组会浪费内存。
要使哈希映射按预期工作,重要的是相等的对象给出相同的哈希码。
如果您仅覆盖 equals()
方法,则 Object.hashCode()
不满足该要求,您还必须覆盖 hashCode()
- 否则 get()
方法将找不到您存储在地图中的对象。
如果你想让两部电影 equals()
如果它们的字段相等,你应该提供一个合适的 hashCode()
方法,就像你做的那样。
让我们看看可能的覆盖组合。
不覆盖任何内容
两部电影不同,最终作为不同的哈希映射条目,可能在同一个桶中,也可能在不同的桶中。
只覆盖 hashCode()
两部电影不同,最终作为同一存储桶中的不同哈希映射条目。如果您仍然使用相等的 Object
定义,那么发明您自己的 hashCode()
实现是无稽之谈。
覆盖 hashCode() 和 equals()
两部电影相等,最终只有一个哈希映射条目,后存储的值获胜。发生这种情况是因为第二个 put()
在哈希码的桶下找到了一个具有相同键的条目,并简单地替换了它的值部分。
只覆盖 equals()
大错!两部电影是相等的,但这并没有反映在 hashCode()
计算中,所以对现有值的搜索是否找到正确的桶只是运气问题。
我有一个 Movie
class 并且 我只覆盖 hashCode()
方法 。请在下面找到 java class
public class Movie {
private String actor;
private String name;
private String releaseYr;
public String getActor() {
return actor;
}
public void setActor(String actor) {
this.actor = actor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getReleaseYr() {
return releaseYr;
}
public void setReleaseYr(String releaseYr) {
this.releaseYr = releaseYr;
}
@Override
public int hashCode() {
return actor.hashCode() + name.hashCode() + releaseYr.hashCode();
}
}
我创建了两个 Movie
对象,两个对象的所有 属性 值都相同,并将它们放在 HashMap
中。下面是代码
import java.util.HashMap;
public class Test {
public static void main(String[] args) {
Movie m1 = new Movie();
m1.setActor("Akshay");
m1.setName("Taskvir");
m1.setReleaseYr("2010");
Movie m2 = new Movie();
m2.setActor("Akshay");
m2.setName("Taskvir");
m2.setReleaseYr("2010");
HashMap<Movie, String> map = new HashMap<Movie, String>();
map.put(m1, "Value of m1");
map.put(m2, "Value of m2");
}
}
我得到了预期的结果。由于我只覆盖了 hashCode() 方法,而且对象的 哈希值相同,所以它们存储在 HashMap table 数组的相同索引位置 中。下面是调试模式下的预期结果。
但是如果我不覆盖 hashCode() 方法而是覆盖 equals() 方法,它们将存储在 HashMap table 数组的相同索引位置。 尽管我可以看到哈希值不同。下面是我的 equals 方法
@Override
public boolean equals(Object obj) {
Movie m1 = (Movie) obj;
boolean result = false;
if (m1.getActor().equals(this.actor) && m1.getName().equals(this.name)
&& m1.getReleaseYr().equals(this.releaseYr)) {
result = true;
}
return result;
}
调试模式下的输出
如果我不覆盖 equals 和 hashCode 方法,那么我也会得到同样的意外结果。
根据我的理解,如果我不覆盖 equals
和 hashCode
方法或只覆盖 equals
方法,那么 m1
和 m2
对象应该存储在不同的位置,因为 m1
和 m2
对象的哈希值不同。但在这种情况下,它没有发生。
有人可以解释一下为什么我的对象存储在相同位置的哈希值不同吗?
我用过Java8.
哈希码有一个 巨大的 范围,从 Integer.MIN_VALUE
到 Integer.MAX_VALUE
,而 HashMap
通常有更少的桶(默认情况下, 16 对于新实例化的 HashMap
,至少对于 OpenJDK 11)。因此,哈希码发生冲突是完全有可能的,甚至是意料之中的,并且多个对象将被添加到同一个桶中。但是,请注意,如果您没有覆盖 hashCode()
,此行为完全是偶然的,不能依赖。
无论哈希代码是如何计算的,通过您的方法或默认来自 Object
class,不同的对象都可以映射到相同的哈希映射桶(数组索引)。哈希码除以数组大小,余数给出桶号
Object.hashCode()
生成的两个哈希码(31622540 和 27844196)在除以 16(初始 HashMap 数组大小)时恰好生成相同的余数 4。
因此,有 40 亿个不同的哈希码可用,其中一些必须在同一个桶中结束,因为为每个哈希映射分配一个 40 亿个元素的数组会浪费内存。
要使哈希映射按预期工作,重要的是相等的对象给出相同的哈希码。
如果您仅覆盖 equals()
方法,则 Object.hashCode()
不满足该要求,您还必须覆盖 hashCode()
- 否则 get()
方法将找不到您存储在地图中的对象。
如果你想让两部电影 equals()
如果它们的字段相等,你应该提供一个合适的 hashCode()
方法,就像你做的那样。
让我们看看可能的覆盖组合。
不覆盖任何内容
两部电影不同,最终作为不同的哈希映射条目,可能在同一个桶中,也可能在不同的桶中。
只覆盖 hashCode()
两部电影不同,最终作为同一存储桶中的不同哈希映射条目。如果您仍然使用相等的 Object
定义,那么发明您自己的 hashCode()
实现是无稽之谈。
覆盖 hashCode() 和 equals()
两部电影相等,最终只有一个哈希映射条目,后存储的值获胜。发生这种情况是因为第二个 put()
在哈希码的桶下找到了一个具有相同键的条目,并简单地替换了它的值部分。
只覆盖 equals()
大错!两部电影是相等的,但这并没有反映在 hashCode()
计算中,所以对现有值的搜索是否找到正确的桶只是运气问题。