虽然哈希值不同,但为啥我的对象存储在同一个位置?

Posted

技术标签:

【中文标题】虽然哈希值不同,但为啥我的对象存储在同一个位置?【英文标题】:Although hash values are different, still why are my objects stored in the same location?虽然哈希值不同,但为什么我的对象存储在同一个位置? 【发布时间】:2019-05-23 03:55:48 【问题描述】:

我有一个Movie 类并且我只覆盖了hashCode() 方法。请在下面找到java类

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 表数组的相同索引位置。以下是调试模式下的预期结果。

但是如果我不重写 hashCode() 方法而是重写 equals() 方法,它们将存储在 HashMap 表数组的相同索引位置。 虽然我可以看到哈希值不同。下面是我的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 方法,那么我也会得到同样的意外结果。

根据我的理解,如果我不覆盖 equalshashCode 方法或只覆盖 equals 方法,那么 m1m2 对象应该存储在不同的位置,因为哈希值不同m1m2 对象。但在这种情况下,它没有发生。

谁能解释一下为什么使用不同的哈希值,我的对象存储在同一个位置?

我使用过 Java 8。

【问题讨论】:

【参考方案1】:

无论哈希码是如何计算的,通过您的方法或Object 类的默认值,不同的对象都可以映射到同一个哈希图桶(数组索引)。哈希码除以数组大小,余数给出桶号。

Object.hashCode()(31622540 和 27844196)生成的两个哈希码在除以 16(初始 HashMap 数组大小)时恰好产生相同的余数 4。

因此,有 40 亿个不同的哈希码可用,其中一些最终必须在同一个存储桶中,因为为每个哈希映射分配 40 亿个元素的数组会浪费内存。

要使哈希映射按预期工作,重要的是相等的对象给出相同的哈希码。

如果您只覆盖 equals() 方法,Object.hashCode() 不满足该要求,您还必须覆盖 hashCode() - 否则 get() 方法将找不到您存储的对象地图。

如果您希望两部电影的字段相等,则为 equals(),您应该提供适当的 hashCode() 方法以及您所做的方式。

让我们看看可能的覆盖组合。

不覆盖任何内容

两部电影是不同的,最终作为不同的哈希映射条目,可能在同一个,也可能在不同的桶中。

只覆盖 hashCode()

两部电影不同,最终在同一个存储桶中作为不同的哈希映射条目。如果您仍然使用 Object 的平等定义,那么发明自己的 hashCode() 实现是无稽之谈。

同时覆盖 hashCode() 和 equals()

两部电影是相等的,最终只有一个哈希映射条目,后面存储的值获胜。发生这种情况是因为第二个put() 在哈希码的桶下找到了一个具有相等键的条目,并简单地替换了它的值部分。

只覆盖equals()

大错特错!两部电影是相等的,但这并没有反映在 hashCode() 计算中,因此搜索现有值是否查找到正确的存储桶只是运气的问题。

【讨论】:

【参考方案2】:

哈希码有一个巨大的范围,从Integer.MIN_VALUEInteger.MAX_VALUE,而HashMap通常有更少的桶(默认情况下,新实例化的HashMap有16个,在至少使用 OpenJDK 11)。因此,完全有可能,甚至可以预料,哈希码会发生冲突,并且多个对象将被添加到同一个存储桶中。但是,请注意,如果您没有覆盖 hashCode(),则此行为完全是偶然的,不能依赖。

【讨论】:

以上是关于虽然哈希值不同,但为啥我的对象存储在同一个位置?的主要内容,如果未能解决你的问题,请参考以下文章

java中哈希表及其应用详解

九. 常用类库向量与哈希6.哈希表及其应用

“key”是不是在 Java HashMap 中存储了两次?

为啥哈希表在存储桶的数组上使用链表?

Java Hashtable 如何根据 hashcode 计算元素的放置位置? [复制]

Java-哈希表