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

相关文章

MySQL中动态生成SQL语句去掉所有字段的空格的操作方法

《MySQL中动态生成SQL语句去掉所有字段的空格的操作方法》在数据库管理过程中,我们常常会遇到需要对表中字段进行清洗和整理的情况,本文将详细介绍如何在MySQL中动态生成SQL语句来去掉所有字段的空... 目录在mysql中动态生成SQL语句去掉所有字段的空格准备工作原理分析动态生成SQL语句在MySQL

Java利用docx4j+Freemarker生成word文档

《Java利用docx4j+Freemarker生成word文档》这篇文章主要为大家详细介绍了Java如何利用docx4j+Freemarker生成word文档,文中的示例代码讲解详细,感兴趣的小伙伴... 目录技术方案maven依赖创建模板文件实现代码技术方案Java 1.8 + docx4j + Fr

Java编译生成多个.class文件的原理和作用

《Java编译生成多个.class文件的原理和作用》作为一名经验丰富的开发者,在Java项目中执行编译后,可能会发现一个.java源文件有时会产生多个.class文件,从技术实现层面详细剖析这一现象... 目录一、内部类机制与.class文件生成成员内部类(常规内部类)局部内部类(方法内部类)匿名内部类二、

使用Jackson进行JSON生成与解析的新手指南

《使用Jackson进行JSON生成与解析的新手指南》这篇文章主要为大家详细介绍了如何使用Jackson进行JSON生成与解析处理,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 核心依赖2. 基础用法2.1 对象转 jsON(序列化)2.2 JSON 转对象(反序列化)3.

java中使用POI生成Excel并导出过程

《java中使用POI生成Excel并导出过程》:本文主要介绍java中使用POI生成Excel并导出过程,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录需求说明及实现方式需求完成通用代码版本1版本2结果展示type参数为atype参数为b总结注:本文章中代码均为

在java中如何将inputStream对象转换为File对象(不生成本地文件)

《在java中如何将inputStream对象转换为File对象(不生成本地文件)》:本文主要介绍在java中如何将inputStream对象转换为File对象(不生成本地文件),具有很好的参考价... 目录需求说明问题解决总结需求说明在后端中通过POI生成Excel文件流,将输出流(outputStre

C/C++随机数生成的五种方法

《C/C++随机数生成的五种方法》C++作为一种古老的编程语言,其随机数生成的方法已经经历了多次的变革,早期的C++版本使用的是rand()函数和RAND_MAX常量,这种方法虽然简单,但并不总是提供... 目录C/C++ 随机数生成方法1. 使用 rand() 和 srand()2. 使用 <random

Flask 验证码自动生成的实现示例

《Flask验证码自动生成的实现示例》本文主要介绍了Flask验证码自动生成的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习... 目录生成图片以及结果处理验证码蓝图html页面展示想必验证码大家都有所了解,但是可以自己定义图片验证码

Python如何在Word中生成多种不同类型的图表

《Python如何在Word中生成多种不同类型的图表》Word文档中插入图表不仅能直观呈现数据,还能提升文档的可读性和专业性,本文将介绍如何使用Python在Word文档中创建和自定义各种图表,需要的... 目录在Word中创建柱形图在Word中创建条形图在Word中创建折线图在Word中创建饼图在Word

nginx生成自签名SSL证书配置HTTPS的实现

《nginx生成自签名SSL证书配置HTTPS的实现》本文主要介绍在Nginx中生成自签名SSL证书并配置HTTPS,包括安装Nginx、创建证书、配置证书以及测试访问,具有一定的参考价值,感兴趣的可... 目录一、安装nginx二、创建证书三、配置证书并验证四、测试一、安装nginxnginx必须有"-