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

相关文章

k8s admin用户生成token方式

《k8sadmin用户生成token方式》用户使用Kubernetes1.28创建admin命名空间并部署,通过ClusterRoleBinding为jenkins用户授权集群级权限,生成并获取其t... 目录k8s admin用户生成token创建一个admin的命名空间查看k8s namespace 的

Java 中的 equals 和 hashCode 方法关系与正确重写实践案例

《Java中的equals和hashCode方法关系与正确重写实践案例》在Java中,equals和hashCode方法是Object类的核心方法,广泛用于对象比较和哈希集合(如HashMa... 目录一、背景与需求分析1.1 equals 和 hashCode 的背景1.2 需求分析1.3 技术挑战1.4

Vue3 如何通过json配置生成查询表单

《Vue3如何通过json配置生成查询表单》本文给大家介绍Vue3如何通过json配置生成查询表单,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录功能实现背景项目代码案例功能实现背景通过vue3实现后台管理项目一定含有表格功能,通常离不开表单

Java使用Javassist动态生成HelloWorld类

《Java使用Javassist动态生成HelloWorld类》Javassist是一个非常强大的字节码操作和定义库,它允许开发者在运行时创建新的类或者修改现有的类,本文将简单介绍如何使用Javass... 目录1. Javassist简介2. 环境准备3. 动态生成HelloWorld类3.1 创建CtC

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

C#使用Spire.XLS快速生成多表格Excel文件

《C#使用Spire.XLS快速生成多表格Excel文件》在日常开发中,我们经常需要将业务数据导出为结构清晰的Excel文件,本文将手把手教你使用Spire.XLS这个强大的.NET组件,只需几行C#... 目录一、Spire.XLS核心优势清单1.1 性能碾压:从3秒到0.5秒的质变1.2 批量操作的优雅

Python使用python-pptx自动化操作和生成PPT

《Python使用python-pptx自动化操作和生成PPT》这篇文章主要为大家详细介绍了如何使用python-pptx库实现PPT自动化,并提供实用的代码示例和应用场景,感兴趣的小伙伴可以跟随小编... 目录使用python-pptx操作PPT文档安装python-pptx基础概念创建新的PPT文档查看

在ASP.NET项目中如何使用C#生成二维码

《在ASP.NET项目中如何使用C#生成二维码》二维码(QRCode)已广泛应用于网址分享,支付链接等场景,本文将以ASP.NET为示例,演示如何实现输入文本/URL,生成二维码,在线显示与下载的完整... 目录创建前端页面(Index.cshtml)后端二维码生成逻辑(Index.cshtml.cs)总结

Python实现数据可视化图表生成(适合新手入门)

《Python实现数据可视化图表生成(适合新手入门)》在数据科学和数据分析的新时代,高效、直观的数据可视化工具显得尤为重要,下面:本文主要介绍Python实现数据可视化图表生成的相关资料,文中通过... 目录前言为什么需要数据可视化准备工作基本图表绘制折线图柱状图散点图使用Seaborn创建高级图表箱线图热

SQLServer中生成雪花ID(Snowflake ID)的实现方法

《SQLServer中生成雪花ID(SnowflakeID)的实现方法》:本文主要介绍在SQLServer中生成雪花ID(SnowflakeID)的实现方法,文中通过示例代码介绍的非常详细,... 目录前言认识雪花ID雪花ID的核心特点雪花ID的结构(64位)雪花ID的优势雪花ID的局限性雪花ID的应用场景