先点赞再看,养成好习惯
先看一个最简略的打印
System.out.println(new Object());
会输入该类的全限定类名和一串字符串:
java.lang.Object@6659c656
@
符号前面的是什么?是 hashcode 还是对象的内存地址?还是其余的什么值?
其实@
前面的只是对象的 hashcode 值,16进制展现的 hashcode 而已,来验证一下:
Object o = new Object();int hashcode = o.hashCode();// toStringSystem.out.println(o);// hashcode 十六进制System.out.println(Integer.toHexString(hashcode));// hashcodeSystem.out.println(hashcode);// 这个办法,也是获取对象的 hashcode;不过和 Object.hashcode 不同的是,该办法会忽视重写的hashcodeSystem.out.println(System.identityHashCode(o));
输入后果:
java.lang.Object@6659c6566659c65617171595101717159510
那对象的 hashcode 到底是怎么生成的呢?真的就是内存地址吗?
本文内容基于 JAVA 8 HotSpot
hashCode 的生成逻辑
JVM 里生成 hashCode 的逻辑并没有那么简略,它提供了好几种策略,每种策略的生成后果都不同。
来看一下 openjdk 源码里生成 hashCode 的外围办法:
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() ; } else if (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 = intptr_t(obj) >> 3 ; value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ; } else if (hashCode == 2) { value = 1 ; // for sensitivity testing } else if (hashCode == 3) { value = ++GVars.hcSequence ; } else if (hashCode == 4) { value = 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
的全局变量管制的,默认为5;而这个变量的定义在另一个头文件里:
product(intx, hashCode, 5, "(Unstable) select hashCode generation algorithm" )
源码里很分明了……(非稳固)抉择 hashCode 生成的算法,而且这里的定义,是能够由 jvm 启动参数来管制的,先来确认下默认值:
java -XX:+PrintFlagsFinal -version | grep hashCodeintx hashCode = 5 {product}openjdk version "1.8.0_282"OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_282-b08)OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.282-b08, mixed mode)
所以咱们能够通过 jvm 的启动参数来配置不同的 hashcode 生成算法,测试不同算法下的生成后果:
-XX:hashCode=N
当初来看看,每种 hashcode 生成算法的不同体现。
第 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(); }
这种生成算法,应用的一种Park-Miller RNG的随机数生成策略。不过须要留神的是……这个随机算法在高并发的时候会呈现自旋期待
第 1 种算法
if (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 = intptr_t(obj) >> 3 ; value = addrBits ^ (addrBits >> 5) ^ GVars.stwRandom ;}
这个算法,真的是对象的内存地址了,间接获取对象的 intptr_t
类型指针
第 2 种算法
if (hashCode == 2) { value = 1 ; // for sensitivity testing}
这个就不必解释了……固定返回 1,应该是用于外部的测试场景。
有趣味的同学,能够试试-XX:hashCode=2
来开启这个算法,看看 hashCode 后果是不是都变成 1 了。
第 3 种算法
if (hashCode == 3) { value = ++GVars.hcSequence ;}
这个算法也很简略,自增嘛,所有对象的 hashCode 都应用这一个自增变量。来试试成果:
System.out.println(new Object());System.out.println(new Object());System.out.println(new Object());System.out.println(new Object());System.out.println(new Object());System.out.println(new Object());//outputjava.lang.Object@144java.lang.Object@145java.lang.Object@146java.lang.Object@147java.lang.Object@148java.lang.Object@149
果然是自增的……有点意思
第 4 种算法
if (hashCode == 4) { value = intptr_t(obj) ;}
这里和第 1 种算法其实区别不大,都是返回对象地址,只是第 1 种算法是一个变体。
第 5 种算法
最初一种,也是默认的生成算法,hashCode 配置不等于 0/1/2/3/4 时应用该算法:
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 ; }
这里是通过以后状态值进行异或(XOR)运算失去的一个 hash 值,相比后面的自增算法和随机算法来说效率更高,但反复率应该也会绝对增高,不过 hashCode 反复又有什么关系呢……
原本 jvm 就不保障这个值肯定不反复,像 HashMap 里的链地址法就是解决 hash 抵触用的
总结
hashCode 能够是内存地址,也能够不是内存地址,甚至能够是 1 这个常数或者自增数!想用什么算法,它都能够!
原创不易,禁止未受权的转载。如果我的文章对您有帮忙,就请点赞/珍藏/关注激励反对一下吧❤❤❤❤❤❤