95% 的程序員都會犯的一個錯誤,Java 默認(rèn) hashCode 對象返回的并不是內(nèi)存地址!
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!
編輯:業(yè)余草
segmentfault.com/a/1190000040152594
推薦:https://www.xttblog.com/?p=5290
?先點(diǎn)贊再看,養(yǎng)成好習(xí)慣
?
95% 的程序員都會犯的一個錯誤,甚至還包含某些知名講師。Java 中 Object 默認(rèn) hashCode 對象返回的并不是內(nèi)存地址!
先看一個最簡單的打印。
System.out.println(new?Object());
會輸出該類的全限定類名和一串字符串:
java.lang.Object@6659c656
@符號后面的是什么?是 hashcode 還是對象的內(nèi)存地址?還是其他的什么值?
其實(shí)@后面的只是對象的 hashcode 值,16進(jìn)制展示的 hashcode 而已,來驗(yàn)證一下:
Object?o?=?new?Object();
int?hashcode?=?o.hashCode();
//?toString
System.out.println(o);
//?hashcode?十六進(jìn)制
System.out.println(Integer.toHexString(hashcode));
//?hashcode
System.out.println(hashcode);
//?這個方法,也是獲取對象的 hashcode;不過和 Object.hashcode 不同的是,該方法會無視重寫的hashcode
System.out.println(System.identityHashCode(o));
輸出結(jié)果:
java.lang.Object@6659c656
6659c656
1717159510
1717159510
那對象的 hashcode 到底是怎么生成的呢?真的就是內(nèi)存地址嗎?
「本文內(nèi)容基于 JAVA 8 HotSpot」
hashCode 的生成邏輯
JVM 里生成 hashCode 的邏輯并沒有那么簡單,它提供了好幾種策略,每種策略的生成結(jié)果都不同。
來看一下 openjdk 源碼里生成 hashCode 的核心方法:https://github.com/openjdk/jdk/blob/7ba83041b1d65545833655293d0976dfd1ffdea8/hotspot/src/share/vm/runtime/synchronizer.cpp#L557。
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;
}
從源碼里可以發(fā)現(xiàn),生成策略是由一個 hashCode 的全局變量控制的,默認(rèn)為5;而這個變量的定義在另一個頭文件里:https://github.com/openjdk/jdk/blob/7ba83041b1d65545833655293d0976dfd1ffdea8/hotspot/src/share/vm/runtime/globals.hpp#L1069。
??product(intx,?hashCode,?5,????????????????????????????????????????????
?????????"(Unstable)?select?hashCode?generation?algorithm"?)?
源碼里很清楚了……「(非穩(wěn)定)選擇 hashCode 生成的算法」,而且這里的定義,是可以由 jvm 啟動參數(shù)來控制的,先來確認(rèn)下默認(rèn)值:
java?-XX:+PrintFlagsFinal?-version?|?grep?hashCode
intx?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 的啟動參數(shù)來配置不同的 hashcode 生成算法,測試不同算法下的生成結(jié)果:」
-XX:hashCode=N
現(xiàn)在來看看,每種 hashcode 生成算法的不同表現(xiàn)。
第 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」的隨機(jī)數(shù)生成策略。不過需要注意的是……這個隨機(jī)算法在高并發(fā)的時候會出現(xiàn)自旋等待
第 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?;
}
這個算法,真的是對象的內(nèi)存地址了,直接獲取對象的 intptr_t 類型指針
第 2 種算法
if?(hashCode?==?2)?{
????value?=?1?;????????????//?for?sensitivity?testing
}
這個就不用解釋了……固定返回 1,應(yīng)該是用于內(nèi)部的測試場景。
有興趣的同學(xué),可以試試-XX:hashCode=2來開啟這個算法,看看 hashCode 結(jié)果是不是都變成 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());
//output
java.lang.Object@144
java.lang.Object@145
java.lang.Object@146
java.lang.Object@147
java.lang.Object@148
java.lang.Object@149
果然是自增的……有點(diǎn)意思
第 4 種算法
if?(hashCode?==?4)?{
????value?=?intptr_t(obj)?;
}
這里和第 1 種算法其實(shí)區(qū)別不大,都是返回對象地址,只是第 1 種算法是一個變體。
第 5 種算法
最后一種,「也是默認(rèn)的生成算法」,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?;
??}
這里是通過當(dāng)前狀態(tài)值進(jìn)行異或(XOR)運(yùn)算得到的一個 hash 值,相比前面的自增算法和隨機(jī)算法來說效率更高,但重復(fù)率應(yīng)該也會相對增高,不過 hashCode 重復(fù)又有什么關(guān)系呢……
本來 jvm 就不保證這個值一定不重復(fù),像 HashMap 里的鏈地址法就是解決 hash 沖突用的
總結(jié)
hashCode 可以是內(nèi)存地址,也可以不是內(nèi)存地址,甚至可以是 1 這個常數(shù)或者自增數(shù)!想用什么算法,它都可以!
