hashCode的生成

2024-08-21 18:58
文章标签 生成 hashcode

本文主要是介绍hashCode的生成,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

hashCode 方法的定义

在 jdk api 中 关于 hashCode 有如下说明:

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Returns a hash code value for the object.
This method is supported for the benefit of hash tables such as those provided by HashMap.
The general contract of hashCode is:Whenever it is invoked on the same object more than once during an execution of a Java application,
the hashCode method must consistently return the same integer,
provided no information used in equals comparisons on the object is modified.
This integer need not remain consistent from one execution of an application to another execution of the same application.
If two objects are equal according to the equals(Object) method,
then calling the hashCode method on each of the two objects must produce the same integer result.
It is not required that if two objects are unequal according to the equals(java.lang.Object) method,
then calling the hashCode method on each of the two objects must produce distinct integer results.
However, the programmer should be aware that producing distinct integer results for unequal objects may improve the performance of hash tables.
As much as is reasonably practical,
the hashCode method defined by class Object does return distinct integers for distinct objects.
(This is typically implemented by converting the internal address of the object into an integer,but this implementation technique is not required by the JavaTM programming language.)

其大致意思如下

 

1
2
3
4
5
6
7
8
9
10
11
12
13
只要在Java应用程序的执行过程中多次调用同一个对象,
hashCode方法必须始终返回相同的整数,
前提是在对象的equals比较中没有使用的信息被修改。
从应用程序的一次执行到同一应用程序的另一次执行,此整数不必保持一致。如果两个对象按照equals(Object)方法相等,
那么在两个对象的每一个上调用hashCode方法必须产生相同的整数结果。
如果两个对象根据equals(java.lang.Object)方法不相等,
则不要求对两个对象中的每个对象调用hashCode方法都必须产生不同的整数结果。
但是,程序员应该知道,为不相等的对象生成不同的整数结果可以提高散列表的性能。尽可能多地合理实用,由类Object定义的hashCode方法确实为不同的对象返回不同的整数。
这通常通过将对象的内部地址转换为整数来实现,但JavaTM编程语言不需要此实现技术。

所以由上可以得到两条有用的信息,同一个对象 hashcode 的值在一次运行中一定相等,并且不同对象的 hashcode 一定不同,但是他还备注通常使用内部地址转换,但是 JAVA 不是使用这种方式实现的,那么怎么实现的呢?

hashCode 实现原理

hashcode 源码

OpenJDK 的源码可以直接查看,所以我们就选择查看一下其源码一看究竟。
我们可以看到src/share/vm/prims/jvm.hsrc/share/vm/prims/jvm.cpp两个文件中有关于 hashcode 的说明如下:

 

1
2
3
4
5
   JVM_ENTRY(jint, JVM_IHashCode(JNIEnv* env, jobject handle))JVMWrapper("JVM_IHashCode");// as implemented in the classic virtual machine; return 0 if object is NULLreturn handle == NULL ? 0 : ObjectSynchronizer::FastHashCode (THREAD, JNIHandles::resolve_non_null(handle)) ;JVM_END

我们继续进入FashHashCode里面查看,其位于src/share/vm/runtime/synchronizer.cpp文件,相对代码比较多,我们只摘取关键部分:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  // Inflate the monitor to set hash codemonitor = ObjectSynchronizer::inflate(Self, obj);// Load displaced header and check it has hash codemark = monitor->header();assert (mark->is_neutral(), "invariant") ;hash = mark->hash();if (hash == 0) {hash = get_next_hash(Self, obj);temp = mark->copy_set_hash(hash); // merge hash code into headerassert (temp->is_neutral(), "invariant") ;test = (markOop) Atomic::cmpxchg_ptr(temp, monitor, mark);if (test != mark) {// The only update to the header in the monitor (outside GC)// is install the hash code. If someone add new usage of// displaced header, please update this codehash = test->hash();assert (test->is_neutral(), "invariant") ;assert (hash != 0, "Trivial unexpected object/monitor header usage.");}}// We finally get the hashreturn hash;

monitor 相关代码我们先略过不理,通过 if 语句我们可以看出,当 hash为0时候需要调用 get_next_hash 生成一个新的 hash,那么我们便可以继续前行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
static inline intptr_t get_next_hash(Thread * Self, oop obj) {intptr_t value = 0 ;if (hashCode == 0) {// This form uses an unguarded global Park-Miller RNG,// so it's possible for two threads to race and generate the same RNG.// On MP system we'll have lots of RW access to a global, so the// mechanism induces lots of coherency traffic.value = os::random() ;} elseif (hashCode == 1) {// This variation has the property of being stable (idempotent)// between STW operations.  This can be useful in some of the 1-0// synchronization schemes.intptr_t addrBits = cast_from_oop<intptr_t>(obj) >> 3 ;value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;} elseif (hashCode == 2) {value = 1 ;            // for sensitivity testing} elseif (hashCode == 3) {value = ++GVars.hcSequence ;} elseif (hashCode == 4) {value = cast_from_oop<intptr_t>(obj) ;} else {// Marsaglia's xor-shift scheme with thread-specific state// This is probably the best overall implementation -- we'll// likely make this the default in future releases.unsigned t = Self->_hashStateX ;t ^= (t << 11) ;Self->_hashStateX = Self->_hashStateY ;Self->_hashStateY = Self->_hashStateZ ;Self->_hashStateZ = Self->_hashStateW ;unsigned v = Self->_hashStateW ;v = (v ^ (v >> 19)) ^ (t ^ (t >> 8)) ;Self->_hashStateW = v ;value = v ;}value &= markOopDesc::hash_mask;if (value == 0) value = 0xBAD ;assert (value != markOopDesc::no_hash, "invariant") ;TEVENT (hashCode: GENERATE) ;return value;
}

通过上述代码我们看到,其实 hashCode 的生成有6中方式
1. 随机数
2. 对象的内存地址的函数
3. 固定值,这个只是为了进行灵敏度测试
4. 递增序列
5. int类型的该对象的内存地址 
6. 结合当前线程和xorshift生成

通过 globals.hpp 我们可以发现,JDK8 默认为5,也就是最后一种。
product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm") 
当然,OpenJDK6,7中用的都是第一种方案,那么问题又来了,既然都是随机数,那么怎么确保每次都一样的呢?

对象头

这里就需要引入一个对象头的概念,每次对象生成以后,都需要找一个地方存储一下这个对象的hashCode和锁信息,这就是对象头,英文称之为 Mark Word。这样一来我们就明白了,每次生成对象以后都会把它的hashCode存起来,这样无论对象怎么在新生代,老年代之间游走都不会改变其hashCode的值,然而事实并没有那么简单。

偏向锁

这时候我们翻回来看刚才略过的内容,ObjectSynchronizer::FastHashCode()里面的其他逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if (UseBiasedLocking) {// NOTE: many places throughout the JVM do not expect a safepoint// to be taken here, in particular most operations on perm gen// objects. However, we only ever bias Java instances and all of// the call sites of identity_hash that might revoke biases have// been checked to make sure they can handle a safepoint. The// added check of the bias pattern is to avoid useless calls to// thread-local storage.if (obj->mark()->has_bias_pattern()) {// Box and unbox the raw reference just in case we cause a STW safepoint.Handle hobj (Self, obj) ;// Relaxing assertion for bug 6320749.assert (Universe::verify_in_progress() ||!SafepointSynchronize::is_at_safepoint(),"biases should not be seen by VM thread here");BiasedLocking::revoke_and_rebias(hobj, false, JavaThread::current());obj = hobj() ;assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");}}

由上述代码我们可以得知,当前对象处于偏向锁时,会清除偏向锁通过从上面取回Mark Word信息。为什么提到取回呢?之前消失了吗?是的,现在就需要解释一下偏向锁了。
Hotspot 的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入 了偏向锁。当一个线程访问同步块并获取锁时,会在对象头和栈帧中的锁记录里存储锁偏向的线程 ID,以后该线程在进入和退出同步块时不需要花费 CAS 操作来加锁和解锁,而只需简单的测试一下对象头的 Mark Word 里是否存储着指向当前线程的偏向锁,如果测试成功,表示线程已经获得了锁,如果测试失败,则需要再测试下 Mark Word 中偏向锁的标识是否设置成 1(表示当前是偏向锁),如果没有设置,则使用 CAS 竞争锁,如果设置了,则尝试使用 CAS 将对象头的偏向锁指向当前线程。所以我们便知道为什么有取回这个概念了。然而代码带没有结束。

轻量级锁

轻量级锁相对比较简单,JVM会在当前的线程栈桢中创建用于存放锁的空间,同时将对象头中的Mark Word复制到锁记录中,也称作 Displaced Mark Word。比较复杂的是重量级锁。

重量级锁

这个时候如果多个线程来竞争资源,就会发生锁膨胀,这样因为需要保存竞争资源需要wait的线程和相关信息,就引入了monitor的概念。于是这时候就把Mark Word存放到了Monitor里面,当然Monitor不仅仅用于存储对象的Mark Word,具体的作用就不是本文的重点了。

hashCode 的用途

hashCode 的唯一性决定了他可以用来生成HashMap的key,同时也能判断对象是否为同一个对象。另外我们再重写他的时候要多加注意,因为JVM会根据它做一些性能优化。

这篇关于hashCode的生成的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1094010

相关文章

Java使用POI-TL和JFreeChart动态生成Word报告

《Java使用POI-TL和JFreeChart动态生成Word报告》本文介绍了使用POI-TL和JFreeChart生成包含动态数据和图表的Word报告的方法,并分享了实际开发中的踩坑经验,通过代码... 目录前言一、需求背景二、方案分析三、 POI-TL + JFreeChart 实现3.1 Maven

MybatisGenerator文件生成不出对应文件的问题

《MybatisGenerator文件生成不出对应文件的问题》本文介绍了使用MybatisGenerator生成文件时遇到的问题及解决方法,主要步骤包括检查目标表是否存在、是否能连接到数据库、配置生成... 目录MyBATisGenerator 文件生成不出对应文件先在项目结构里引入“targetProje

Python使用qrcode库实现生成二维码的操作指南

《Python使用qrcode库实现生成二维码的操作指南》二维码是一种广泛使用的二维条码,因其高效的数据存储能力和易于扫描的特点,广泛应用于支付、身份验证、营销推广等领域,Pythonqrcode库是... 目录一、安装 python qrcode 库二、基本使用方法1. 生成简单二维码2. 生成带 Log

Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南

《Python使用Pandas库将Excel数据叠加生成新DataFrame的操作指南》在日常数据处理工作中,我们经常需要将不同Excel文档中的数据整合到一个新的DataFrame中,以便进行进一步... 目录一、准备工作二、读取Excel文件三、数据叠加四、处理重复数据(可选)五、保存新DataFram

SpringBoot生成和操作PDF的代码详解

《SpringBoot生成和操作PDF的代码详解》本文主要介绍了在SpringBoot项目下,通过代码和操作步骤,详细的介绍了如何操作PDF,希望可以帮助到准备通过JAVA操作PDF的你,项目框架用的... 目录本文简介PDF文件简介代码实现PDF操作基于PDF模板生成,并下载完全基于代码生成,并保存合并P

详解Java中如何使用JFreeChart生成甘特图

《详解Java中如何使用JFreeChart生成甘特图》甘特图是一种流行的项目管理工具,用于显示项目的进度和任务分配,在Java开发中,JFreeChart是一个强大的开源图表库,能够生成各种类型的图... 目录引言一、JFreeChart简介二、准备工作三、创建甘特图1. 定义数据集2. 创建甘特图3.

AI一键生成 PPT

AI一键生成 PPT 操作步骤 作为一名打工人,是不是经常需要制作各种PPT来分享我的生活和想法。但是,你们知道,有时候灵感来了,时间却不够用了!😩直到我发现了Kimi AI——一个能够自动生成PPT的神奇助手!🌟 什么是Kimi? 一款月之暗面科技有限公司开发的AI办公工具,帮助用户快速生成高质量的演示文稿。 无论你是职场人士、学生还是教师,Kimi都能够为你的办公文

pdfmake生成pdf的使用

实际项目中有时会有根据填写的表单数据或者其他格式的数据,将数据自动填充到pdf文件中根据固定模板生成pdf文件的需求 文章目录 利用pdfmake生成pdf文件1.下载安装pdfmake第三方包2.封装生成pdf文件的共用配置3.生成pdf文件的文件模板内容4.调用方法生成pdf 利用pdfmake生成pdf文件 1.下载安装pdfmake第三方包 npm i pdfma

poj 1258 Agri-Net(最小生成树模板代码)

感觉用这题来当模板更适合。 题意就是给你邻接矩阵求最小生成树啦。~ prim代码:效率很高。172k...0ms。 #include<stdio.h>#include<algorithm>using namespace std;const int MaxN = 101;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int n

poj 1287 Networking(prim or kruscal最小生成树)

题意给你点与点间距离,求最小生成树。 注意点是,两点之间可能有不同的路,输入的时候选择最小的,和之前有道最短路WA的题目类似。 prim代码: #include<stdio.h>const int MaxN = 51;const int INF = 0x3f3f3f3f;int g[MaxN][MaxN];int P;int prim(){bool vis[MaxN];