【问题标题】:What is an object's hash code if hashCode() is not overridden?如果 hashCode() 没有被覆盖,那么对象的哈希码是多少?
【发布时间】:2023-03-17 21:34:02
【问题描述】:

如果 hashCode() 方法没有被覆盖,那么在 Java 中对任何对象调用 hashCode() 的结果是什么?

【问题讨论】:

  • 查看 System.identityHashCode() 它会为您提供默认哈希码 - 如果您没有覆盖该方法,则会返回该哈希码。
  • 这取决于您使用的 JVM。热点见this

标签: java object hashcode


【解决方案1】:

默认的 hashCode() 实现与对象的内存地址无关。 在 openJDK 版本 6 和 7 中,它是一个随机生成的数字。在 8 和 9 中,它是一个基于线程状态的数字。

参考此链接:hashCode != address

所以身份哈希生成的结果(hashCode()方法的默认实现返回的值)生成一次,并缓存在对象的头部中。

如果你想了解更多关于这方面的信息,你可以通过 OpenJDK 来定义 hashCode() 的入口点

src/share/vm/prims/jvm.h

src/share/vm/prims/jvm.cpp

如果您浏览上面的这个目录,似乎数百行函数似乎要复杂得多。因此,为了简化这一点,表示默认哈希码实现的天真方式如下所示,

if (obj.hash() == 0) {
    obj.set_hash(generate_new_hash()); 
} 
return obj.hash();

【讨论】:

    【解决方案2】:

    hashCode() 的实现可能因类而异,但hashCode() 的合同非常具体,并在Javadocs 中明确明确地说明:

    返回对象的哈希码值。支持这种方法是为了哈希表,例如 java.util.Hashtable 提供的哈希表。

    hashCode的通用合约是:

    • 只要在 Java 应用程序执行期间对同一个对象多次调用,hashCode 方法必须始终返回相同的整数,前提是没有修改对象上的 equals 比较中使用的信息。该整数不需要从应用程序的一次执行到同一应用程序的另一次执行保持一致。
    • 如果根据 equals(Object) 方法,两个对象相等,则对两个对象中的每一个调用 hashCode 方法必须产生相同的整数结果。
    • 如果根据 equals(java.lang.Object) 方法,如果两个对象不相等,则不要求对两个对象中的每一个调用 hashCode 方法必须产生不同的整数结果。但是,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。

    在合理可行的情况下,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)

    hashCode()equals() 密切相关,如果你覆盖 equals(),你也应该覆盖 hashCode()

    【讨论】:

    • “hashCode() 与 equals() 密切相关,如果你实现一个,你应该实现另一个”并不完全正确。如果 equals 被覆盖,您只需要覆盖 hashCode。在不覆盖 equals 的情况下覆盖 hashCode 在技术上是有效的。
    • @Steve Kuo:很公平。我根据您的评论重新措辞了最后一句话。
    【解决方案3】:

    在 HotSpot JVM 中,默认情况下,在第一次调用非重载 Object.hashCodeSystem.identityHashCode 时会生成一个随机数并将其存储在对象头中。随后对Object.hashCodeSystem.identityHashCode 的调用只是从标头中提取此值。默认情况下,它与对象内容或对象位置没有任何共同之处,只是随机数。此行为由-XX:hashCode=n HotSpot JVM 选项控制,该选项具有以下可能值:

    • 0:使用全局随机生成器。这是 Java 7 中的默认设置。它的缺点是来自多个线程的并发调用可能会导致竞争条件,这将导致为不同的对象生成相同的 hashCode。此外,在高并发环境中,由于争用(使用来自不同 CPU 内核的相同内存区域),可能会出现延迟。
    • 5:使用一些线程局部异或移位随机生成器,它没有以前的缺点。这是 Java 8 中的默认设置。
    • 1:使用对象指针与一些在“停止世界”事件中更改的随机值混合,因此在停止世界事件(如垃圾收集)之间生成的哈希码是稳定的(用于测试/调试目的)
    • 2:始终使用1(用于测试/调试目的)
    • 3:使用自动递增数字(出于测试/调试目的,还使用全局计数器,因此可能存在争用和竞争条件)
    • 4:如有必要,使用修剪为 32 位的对象指针(用于测试/调试目的)

    请注意,即使您设置了-XX:hashCode=4,hashCode 也不会总是指向对象地址。对象可以稍后移动,但 hashCode 将保持不变。此外,对象地址分布不均(如果您的应用程序使用的内存不多,大多数对象将彼此靠近),因此如果使用此选项,您最终可能会得到不平衡的哈希表。

    【讨论】:

    • 这是最好的解释,谢谢!因为-XX:hashCode=n是一个实验特性,需要先启用它然后使用上面的标志,所以组合的vm标志是:-XX:+UnlockExperimentalVMOptions -XX:hashCode=4,在java-11中测试。
    【解决方案4】:

    您必须在每个覆盖 equals 的类中覆盖 hashCode。 否则将导致违反 Object.hashCode 的一般约定,这将阻止您的类在 @987654321 中正常运行@s, including HashMap, HashSet, and Hashtable.

    【讨论】:

      【解决方案5】:

      通常,如果您不覆盖它,hashCode() 只会返回对象在内存中的地址。

      来自1

      在合理可行的情况下,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)

      【讨论】:

      • 我强烈反对“转换内部地址”,我一直想知道 Sun/Oracle 是否会从他们的 javadocs 中删除该行。无法保证对象的内部地址在 JVM 中保持不变,其垃圾收集器可能会在堆压缩期间移动它。
      • JavaDoc 引用是正确的,但答案不是。对象的地址多年未使用。
      • @ernesto 不,他们并非一直都是独一无二的。我在我的 JVM 上对其进行了测试,发现在 120,000 个对象之后,我开始看到重复的哈希码。你可以在这里阅读更多内容stackoverflow.com/questions/40931088/…
      • 正如其他人所说,文档中提到内存地址是不恰当的,但最后,它得到了修复,首先是the “typically” was removed,然后它说hashCode可能是也可能不是在某个时间点实现为对象内存地址的某个函数”,然后,the mentioning of addresses got removed completely,消除了任何混乱的根源。案件结案。
      【解决方案6】:

      返回 6 位十六进制数。这通常是对象被寻址的槽的内存位置。从算法本身来看,我猜 JDK 做了双重哈希(本机实现),这是开放寻址的最佳哈希函数之一。这种双重哈希方案大大降低了冲突的可能性。

      下面的帖子会给出一个支持性的想法-

      Java - HashMap confusion about collision handling and the get() method

      【讨论】:

        【解决方案7】:

        不是真正的答案,而是添加到我之前的评论中

        无法保证对象的内部地址在 JVM 中保持不变,其垃圾收集器可能会在堆压缩期间移动它。

        我试图做这样的事情:

        public static void main(String[] args) {
            final Object object = new Object();
            while (true) {
                int hash = object.hashCode();
                int x = 0;
                Runtime r = Runtime.getRuntime();
                List<Object> list = new LinkedList<Object>();
                while (r.freeMemory() / (double) r.totalMemory() > 0.3) {
                    Object p = new Object();
                    list.add(p);
                    x += object.hashCode();//ensure optimizer or JIT won't remove this
                }
                System.out.println(x);
                list.clear();
                r.gc();
                if (object.hashCode() != hash) {
                    System.out.println("Voila!");
                    break;
                }
            }
        }
        

        但是哈希码确实没有改变……谁能告诉我 Sun 的 JDK 是如何实现 Obect.hashcode 的?

        【讨论】:

        【解决方案8】:

        如果 hashcode 没有被覆盖,你将调用 Object 的 hashcode,这里是它的 javadoc 的摘录:

        在合理的情况下,Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。 (这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)

        【讨论】:

          【解决方案9】:

          对于equals(),具有不同哈希码的两个对象不能相等

          a.hashCode() != b.hashCode() 必须隐含!a.equals(b)

          但是,对于 equals() 不相等的两个对象可以具有相同的哈希码。如果许多对象具有相同的哈希码,则将这些对象存储在集合或映射中的效率会降低。

          【讨论】:

            【解决方案10】:

            默认的哈希码实现给出了 jvm 中对象的内部地址,作为 32 位整数。因此,两个不同的(在内存中的)对象会有不同的哈希码。

            这与equals的默认实现是一致的。如果你想为你的对象覆盖 equals,你必须调整 hashCode 以使它们保持一致。

            请参阅http://www.ibm.com/developerworks/java/library/j-jtp05273.html 以获得良好的概述。

            【讨论】:

              【解决方案11】:

              哈希码对于将对象存储在集合中很有用,例如哈希集。通过允许 Object 将 Hashcode 定义为唯一的东西,它允许 HashSet 的算法有效地工作。

              对象本身使用对象在内存中的地址,这是非常独特的,但如果两个不同的对象(例如两个相同的字符串)应该被认为是相同的,即使它们在内存中是重复的,也可能不是很有用。

              【讨论】:

                【解决方案12】:

                您应该尝试实现哈希码,以便不同的对象会给出不同的结果。我认为没有标准的方法可以做到这一点。

                阅读这篇文章了解一些information

                【讨论】:

                  猜你喜欢
                  • 2014-02-17
                  • 2013-08-02
                  • 2020-06-06
                  • 2015-09-07
                  • 1970-01-01
                  • 1970-01-01
                  • 2014-07-01
                  • 1970-01-01
                  • 2019-03-21
                  相关资源
                  最近更新 更多