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

相关文章

Python实现自动化Word文档样式复制与内容生成

《Python实现自动化Word文档样式复制与内容生成》在办公自动化领域,高效处理Word文档的样式和内容复制是一个常见需求,本文将展示如何利用Python的python-docx库实现... 目录一、为什么需要自动化 Word 文档处理二、核心功能实现:样式与表格的深度复制1. 表格复制(含样式与内容)2

python如何生成指定文件大小

《python如何生成指定文件大小》:本文主要介绍python如何生成指定文件大小的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录python生成指定文件大小方法一(速度最快)方法二(中等速度)方法三(生成可读文本文件–较慢)方法四(使用内存映射高效生成

Maven项目中集成数据库文档生成工具的操作步骤

《Maven项目中集成数据库文档生成工具的操作步骤》在Maven项目中,可以通过集成数据库文档生成工具来自动生成数据库文档,本文为大家整理了使用screw-maven-plugin(推荐)的完... 目录1. 添加插件配置到 pom.XML2. 配置数据库信息3. 执行生成命令4. 高级配置选项5. 注意事

MybatisX快速生成增删改查的方法示例

《MybatisX快速生成增删改查的方法示例》MybatisX是基于IDEA的MyBatis/MyBatis-Plus开发插件,本文主要介绍了MybatisX快速生成增删改查的方法示例,文中通过示例代... 目录1 安装2 基本功能2.1 XML跳转2.2 代码生成2.2.1 生成.xml中的sql语句头2

使用Python自动化生成PPT并结合LLM生成内容的代码解析

《使用Python自动化生成PPT并结合LLM生成内容的代码解析》PowerPoint是常用的文档工具,但手动设计和排版耗时耗力,本文将展示如何通过Python自动化提取PPT样式并生成新PPT,同时... 目录核心代码解析1. 提取 PPT 样式到 jsON关键步骤:代码片段:2. 应用 JSON 样式到

SpringBoot实现二维码生成的详细步骤与完整代码

《SpringBoot实现二维码生成的详细步骤与完整代码》如今,二维码的应用场景非常广泛,从支付到信息分享,二维码都扮演着重要角色,SpringBoot是一个非常流行的Java基于Spring框架的微... 目录一、环境搭建二、创建 Spring Boot 项目三、引入二维码生成依赖四、编写二维码生成代码五

Android与iOS设备MAC地址生成原理及Java实现详解

《Android与iOS设备MAC地址生成原理及Java实现详解》在无线网络通信中,MAC(MediaAccessControl)地址是设备的唯一网络标识符,本文主要介绍了Android与iOS设备M... 目录引言1. MAC地址基础1.1 MAC地址的组成1.2 MAC地址的分类2. android与I

PyQt5+Python-docx实现一键生成测试报告

《PyQt5+Python-docx实现一键生成测试报告》作为一名测试工程师,你是否经历过手动填写测试报告的痛苦,本文将用Python的PyQt5和python-docx库,打造一款测试报告一键生成工... 目录引言工具功能亮点工具设计思路1. 界面设计:PyQt5实现数据输入2. 文档生成:python-

IDEA自动生成注释模板的配置教程

《IDEA自动生成注释模板的配置教程》本文介绍了如何在IntelliJIDEA中配置类和方法的注释模板,包括自动生成项目名称、包名、日期和时间等内容,以及如何定制参数和返回值的注释格式,需要的朋友可以... 目录项目场景配置方法类注释模板定义类开头的注释步骤类注释效果方法注释模板定义方法开头的注释步骤方法注

Python如何自动生成环境依赖包requirements

《Python如何自动生成环境依赖包requirements》:本文主要介绍Python如何自动生成环境依赖包requirements问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑... 目录生成当前 python 环境 安装的所有依赖包1、命令2、常见问题只生成当前 项目 的所有依赖包1、