我一直在研究 Java 对象的内部结构,并对如何管理 hashCode 值感到困惑。据我了解,Java中的hashCode方法返回一个32位整数。然而,这个 hashCode 存储在对象的标头中,特别是 25 位标记字中。
这向我提出了几个问题:
如何在不丢失某些数据位的情况下将 32 位 hashCode 存储在 25 位标记字中?即使由于这种位长差异而导致数据丢失,为什么当我hashCode()再次调用时,它仍然检索到原始的 hashCode 值,而没有任何明显的数据丢失?
如果您能深入了解 Java 如何做到这一点,我们将不胜感激。
首先也是最重要的是:所有这些在很大程度上都是实现细节,而不是由规范定义的。我专门讨论了最近的 OpenJDK 版本(我正在测试 JDK 17,但这种行为似乎存在了一段时间),但没有任何迹象表明其他 JDK 甚至 OpenJDK 的未来版本可以改变这一切。
接下来,区分对象的身份哈希码和它的哈希码很重要。
身份哈希码是由 JVM 决定的值,该值在对象的生命周期内保持不变,并且不会受到 Java 代码的影响(即覆盖hashCode()对此没有影响)。该值可以通过调用System.identityHashCode(obj)获取。
另一方面,哈希码是 Java 程序员最常与之交互的内容:在对象上调用时的返回值hashCode()。虽然每当任何内容存储在HashMapor HashSet(或类似结构)中时这都是一个重要的值,但 JVM 本身并不特别关心它。即使确实如此,它也无法将其存储在对象标头中,因为hashCode()每次调用它时都可能返回不同的值。
这两个定义以一种重要的方式相互作用:hashCode()的方法java.lang.Object(以及该方法未被重写的任何其他对象的方法)将返回身份哈希代码。因此,如果没有定义其他内容,可以说身份哈希码是哈希码的默认值。
查看相关代码后确实看起来在32位平台上最多有25位空间来存储身份哈希码。
但是hashCode被定义为32位宽,那怎么可能呢?
很简单:这些平台上的身份哈希码永远不会使用超过 25 个位,因此所有未存储的位都已知/假定为零。
虽然我没有找到决定这一点的具体位置(我也没有仔细观察),但可以使用如下代码轻松验证这一点:
public class MyClass {
public static void main(String args[]) {
int minLeadingZeroes = 32;
for (int i = 0; i < 1_000_000; i++) {
int hash = System.identityHashCode(new Object());
minLeadingZeroes = Math.min(minLeadingZeroes, Integer.numberOfLeadingZeros(hash));
}
System.out.println("Smallest number of leading zeroes in identity hash codes of 1000000 objects = " + minLeadingZeroes);
}
}
Run Code Online (Sandbox Code Playgroud)
当使用 64 位 JVM 运行时,会打印
public class MyClass {
public static void main(String args[]) {
int minLeadingZeroes = 32;
for (int i = 0; i < 1_000_000; i++) {
int hash = System.identityHashCode(new Object());
minLeadingZeroes = Math.min(minLeadingZeroes, Integer.numberOfLeadingZeros(hash));
}
System.out.println("Smallest number of leading zeroes in identity hash codes of 1000000 objects = " + minLeadingZeroes);
}
}
Run Code Online (Sandbox Code Playgroud)
而在 32 位 JVM 上它会打印
Smallest number of leading zeroes in identity hash codes of 1000000 objects = 1
Run Code Online (Sandbox Code Playgroud)
诚然,这不是绝对的证据,但在测试一百万个物体时,这些值不太可能是巧合。
另请注意,即使在 64 位 OpenJDK 构建中,身份哈希代码也最多使用 31 位(如上面链接的实现的注释中所述),尽管有大量空闲空间(在这种情况下许多位未使用)。
| 归档时间: |
|
| 查看次数: |
141 次 |
| 最近记录: |