如果 hashCode() 方法没有被覆盖,那么在 Java 中对任何对象调用 hashCode() 的结果是什么?
12 回答
在 HotSpot JVM 中默认在第一次调用时非重载Object.hashCode
或System.identityHashCode
随机数生成并存储在对象头中。随后调用Object.hashCode
或System.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 将保持不变。对象地址分布也很差(如果您的应用程序使用的内存不多,大多数对象将彼此靠近),因此如果使用此选项,您最终可能会得到不平衡的哈希表。
通常,如果您不覆盖它,hashCode() 只会返回对象在内存中的地址。
从1开始:
在合理可行的情况下,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。(这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)
的实现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()
。
如果没有覆盖哈希码,您将调用 Object 的哈希码,这是其 javadoc 的摘录:
在合理可行的情况下,由 Object 类定义的 hashCode 方法确实为不同的对象返回不同的整数。(这通常通过将对象的内部地址转换为整数来实现,但 JavaTM 编程语言不需要这种实现技术。)
您必须在每个覆盖 equals 的类中覆盖 hashCode。conjunction with all hash-based collection
不这样做将导致违反 Object.hashCode 的一般约定,这将阻止您的类在s 中正常运行,including HashMap, HashSet, and Hashtable.
默认的 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();
默认的哈希码实现给出了 jvm 中对象的内部地址,作为 32 位整数。因此,两个不同的(内存中的)对象将具有不同的哈希码。
这与equals的默认实现是一致的。如果你想为你的对象覆盖 equals,你必须调整 hashCode 以使它们保持一致。
请参阅http://www.ibm.com/developerworks/java/library/j-jtp05273.html以获得良好的概述。
您应该尝试实现哈希码,以便不同的对象会给出不同的结果。我认为没有标准的方法可以做到这一点。
阅读本文以获取一些信息。
哈希码可用于将对象存储在集合中,例如哈希集。通过允许 Object 将 Hashcode 定义为独特的东西,它允许 HashSet 的算法有效地工作。
对象本身使用对象在内存中的地址,这是非常独特的,但如果两个不同的对象(例如两个相同的字符串)应该被认为是相同的,即使它们在内存中重复,也可能不是很有用。
具有不同哈希码的两个对象在 equals() 中不能相等
a.hashCode() != b.hashCode()
必须暗示!a.equals(b)
但是,对于 equals() 不相等的两个对象可以具有相同的哈希码。如果许多对象具有相同的哈希码,则将这些对象存储在集合或映射中的效率会降低。
不是真正的答案,而是添加到我之前的评论中
不能保证对象的内部地址在 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 的吗?
返回 6 位十六进制数。这通常是对象被寻址的槽的内存位置。从算法本身来看,我猜 JDK 做了双重哈希(本机实现),这是开放寻址的最佳哈希函数之一。这种双重哈希方案大大降低了冲突的可能性。
以下帖子将提供一个支持性的想法 -