細(xì)品Java8中hashCode方法的使用
簡(jiǎn)介
散列函數(shù)(英語(yǔ):Hash function)又稱散列算法、哈希函數(shù),是一種從任何一種數(shù)據(jù)中創(chuàng)建小的數(shù)字“指紋”的方法。散列函數(shù)把消息或數(shù)據(jù)壓縮成摘要,使得數(shù)據(jù)量變小,將數(shù)據(jù)的格式固定下來(lái)。
Java語(yǔ)言對(duì)hashCode的應(yīng)用
主要用途
- hashcode是Object中的函數(shù),所有類(lèi)都擁有的一個(gè)函數(shù),主要返回每個(gè)對(duì)象的hash值,主要用于哈希表中,如HashMap、HashTable、HashSet。
- 在這里需要注意的是,他就是為了在一些對(duì)象數(shù)組里面存儲(chǔ)的時(shí)候可以節(jié)省空間。(我在這里一直有個(gè)誤會(huì),就是hashCode 也會(huì)應(yīng)用于對(duì)象的比較,主要比較的是對(duì)象的是否有被改變過(guò),其實(shí)我們?cè)谶M(jìn)行比較的時(shí)候可以不進(jìn)進(jìn)行重寫(xiě)hashCode,單個(gè)的equals就可以保證這個(gè)對(duì)象是否相等。
- 但是很多面試官都會(huì)問(wèn)到,你重寫(xiě)了equals 不重寫(xiě)hashcode 可以嗎?不一定,當(dāng)你重寫(xiě)的equals是那種兩個(gè)對(duì)象所有值都相等的情況下的時(shí)候,我們就不需要重寫(xiě)。因?yàn)檫@樣他就符合我們的正常邏輯,就是equals相等hashcode值一定相等。但是如果你的equals定義是只要這個(gè)對(duì)象中某個(gè)值相等就代表,這個(gè)對(duì)象相等,那么傳統(tǒng)觀念就被打破了。所以你就得按照你的equals來(lái)重寫(xiě)你的hashcode。保持一致。
Java 中hashcode存儲(chǔ)的位置
存儲(chǔ)在對(duì)象頭markWord,如下圖(深入理解Java虛擬機(jī))

我們知道了他是存儲(chǔ)的位置,那他是什么時(shí)候存儲(chǔ)進(jìn)去的呢? 在Java中所有的對(duì)象都是有hashcode嗎?
Java中HashCode的實(shí)現(xiàn):
在Java中Object.class中有hashCode方法,方法是native 方法,實(shí)現(xiàn)就是在JVM中實(shí)現(xiàn)的,也就是說(shuō)他是使用C語(yǔ)言實(shí)現(xiàn)的。
實(shí)現(xiàn)方式:OpenJDK8 默認(rèn)hashCode的計(jì)算方法是通過(guò)和當(dāng)前線程有關(guān)的一個(gè)隨機(jī)數(shù)+三個(gè)確定值,運(yùn)用Marsaglia's xorshift scheme隨機(jī)數(shù)算法得到的一個(gè)隨機(jī)數(shù)。和對(duì)象內(nèi)存地址無(wú)關(guān)。三個(gè)確定確定值分別是:
// thread-specific hashCode stream generator state - Marsaglia shift-xor form //隨機(jī)數(shù) _hashStateX = os::random() ; //確定值1 _hashStateY = 842502087 ; //確定值2 _hashStateZ = 0x8767 ; // (int)(3579807591LL & 0xffff) //確定值3 _hashStateW = 273326509 ;
可以通過(guò)在JVM啟動(dòng)參數(shù)中添加-XX:hashCode=4,改變默認(rèn)的hashCode計(jì)算方式。
為什么要重寫(xiě)hashCode
如上文提到,我們不按傳統(tǒng)規(guī)則重寫(xiě)了equals方法,所以為了不違反規(guī)則也就得重寫(xiě)hashCode。
源碼中hashcode的重寫(xiě),如hashMap中
如果m1.entrySet( ).equals(m2.entrySet()),則兩個(gè)映射m1和 m2表示相同的映射 。這樣可確保 equals方法可在Map接口的不同實(shí)現(xiàn)中正常工作。
static <K, V> boolean equals(Map<K, V> source, Object object) {
if (source == object) {
return true;
} else if (source != null && object instanceof Map) {
final Map<K, V> map = (Map<K, V>) object;
if (source.size() != map.size()) {
return false;
} else {
try {
return source.forAll(map::contains);
} catch (ClassCastException e) {
return false;
}
}
} else {
return false;
}
}
映射的哈希碼定義為映射的entrySet()視圖中每個(gè)條目的哈希碼之和 。這確保了m1.equals(m2) 隱含了對(duì)任何兩個(gè)映射 m1和m2的m1.hashCode()== m2.hashCode(),這是的總合同要求的 。Object.hashCode()
@Override
public int hashCode() {
return Collections.hashUnordered(this);
}
// hashes the elements regardless of their order
static int hashUnordered(Iterable<?> iterable) {
return hash(iterable, (acc, hash) -> acc + hash);
}
注意點(diǎn) hashMap重寫(xiě)hashCode 和 計(jì)算hash桶位置的是不同的,這兩個(gè)可不敢弄混了,我是弄混了。 下來(lái)我們?cè)倏纯磆ash桶下表的計(jì)算。jdk 1.8中的。
/ ** *計(jì)算key.hashCode()并將(XOR)散列的較高位*擴(kuò)展到較低位。
* 因?yàn)樵摫硎褂?的冪次掩碼,所以*僅在當(dāng)前掩碼上方的位中發(fā)生變化的*哈希集將**總是發(fā)生沖突。 (眾所周知的示例是Float鍵集*在小表中保存連續(xù)的整數(shù)。)
*因此,我們*應(yīng)用了一種變換,向下擴(kuò)展了較高位的影響。在速度,效用和比特?cái)U(kuò)展*質(zhì)量之間需要權(quán)衡。由于許多常見(jiàn)的哈希集*已經(jīng)合理地分布了(因此不能從*擴(kuò)展*中受益),并且由于我們使用樹(shù)來(lái)處理bin中的大量*沖突集,因此我們僅以*最便宜&的方式對(duì)一些移位后的位進(jìn)行XOR運(yùn)算,減少系統(tǒng)損失,以及*合并最高位的影響,否則由于表的限制,這些位將永遠(yuǎn)不會(huì)在索引計(jì)算中使用
* /
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
總結(jié)
- hashCode的簡(jiǎn)介
- Java 中 Object.hashCode()的實(shí)現(xiàn)
- 為什么要重寫(xiě)hashCode()?不打破傳統(tǒng)規(guī)則
- HashMap中hashCode方法的重寫(xiě)。
- HashMap中hash桶的hash計(jì)算。
參考
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#hashCode()
https://docs.oracle.com/javase/8/docs/api/java/util/Map.html#equals(java.lang.Object)
https://juejin.cn/post/6844903487432556551
到此這篇關(guān)于細(xì)品Java8中hashCode方法的使用的文章就介紹到這了,更多相關(guān)Java8 hashCode內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java基礎(chǔ)之三大控制流程結(jié)構(gòu)
這篇文章主要介紹了Java基礎(chǔ)之三大控制流程結(jié)構(gòu),文中有非常詳細(xì)的代碼示例,對(duì)正在學(xué)習(xí)java基礎(chǔ)的小伙伴們有非常好的幫助,需要的朋友可以參考下2021-04-04
淺談Ribbon、Feign和OpenFeign的區(qū)別
這篇文章主要介紹了淺談Ribbon、Feign和OpenFeign的區(qū)別。具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
Java?Shell?springboot通用Shell啟動(dòng)腳本方式
這篇文章主要介紹了Java?Shell?springboot通用Shell啟動(dòng)腳本方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
Java詳細(xì)分析連接數(shù)據(jù)庫(kù)的流程
Java數(shù)據(jù)庫(kù)連接,JDBC是Java語(yǔ)言中用來(lái)規(guī)范客戶端程序如何來(lái)訪問(wèn)數(shù)據(jù)庫(kù)的應(yīng)用程序接口,提供了諸如查詢和更新數(shù)據(jù)庫(kù)中數(shù)據(jù)的方法。JDBC也是Sun Microsystems的商標(biāo)。我們通常說(shuō)的JDBC是面向關(guān)系型數(shù)據(jù)庫(kù)的2022-05-05
在springboot3微項(xiàng)目中如何用idea批量創(chuàng)建單元測(cè)試邏輯
這篇文章主要介紹了在SpringBoot3項(xiàng)目中使用IntelliJIDEA批量創(chuàng)建單元測(cè)試包括準(zhǔn)備工作(確保項(xiàng)目配置正確,添加測(cè)試依賴),使用IntelliJIDEA創(chuàng)建測(cè)試,感興趣的朋友一起看看吧2024-10-10

