真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

如何Java中的hashCode()方法

本篇內(nèi)容主要講解“如何Java中的hashCode()方法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“如何Java中的hashCode()方法”吧!

十年專注建站、設(shè)計、互聯(lián)網(wǎng)產(chǎn)品定制網(wǎng)站開發(fā)服務(wù),業(yè)務(wù)涵蓋成都品牌網(wǎng)站建設(shè)、商城網(wǎng)站定制開發(fā)、小程序定制開發(fā)、軟件系統(tǒng)開發(fā)、重慶App定制開發(fā)等。憑借多年豐富的經(jīng)驗,我們會仔細(xì)了解每個客戶的需求而做出多方面的分析、設(shè)計、整合,為客戶設(shè)計出具風(fēng)格及創(chuàng)意性的商業(yè)解決方案,成都創(chuàng)新互聯(lián)公司更提供一系列網(wǎng)站制作和網(wǎng)站推廣的服務(wù),以推動各中小企業(yè)全面信息數(shù)字化,并利用創(chuàng)新技術(shù)幫助各行業(yè)提升企業(yè)形象和運(yùn)營效率。

Object 類中就包含了 hashCode() 方法:

@HotSpotIntrinsicCandidate public native int hashCode();

意味著所有的類都會有一個 hashCode() 方法,該方法會返回一個 int 類型的值。由于 hashCode() 方法是一個本地方法(native  關(guān)鍵字修飾的方法,用 C/C++ 語言實現(xiàn),由 Java 調(diào)用),意味著 Object 類中并沒有給出具體的實現(xiàn)。

具體的實現(xiàn)可以參考 jdk/src/hotspot/share/runtime/synchronizer.cpp(源碼可以到 GitHub 上  OpenJDK 的倉庫中下載)。get_next_hash() 方法會根據(jù) hashCode 的取值來決定采用哪一種哈希值的生成策略。

如何Java中的hashCode()方法

并且 hashCode() 方法被 @HotSpotIntrinsicCandidate 注解修飾,說明它在 HotSpot  虛擬機(jī)中有一套高效的實現(xiàn),基于 CPU 指令。

那大家有沒有想過這樣一個問題:為什么 Object 類需要一個 hashCode() 方法呢?

在 Java 中,hashCode() 方法的主要作用就是為了配合哈希表使用的。

哈希表(Hash  Table),也叫散列表,是一種可以通過關(guān)鍵碼值(key-value)直接訪問的數(shù)據(jù)結(jié)構(gòu),它最大的特點(diǎn)就是可以快速實現(xiàn)查找、插入和刪除。其中用到的算法叫做哈希,就是把任意長度的輸入,變換成固定長度的輸出,該輸出就是哈希值。像  MD5、SHA1 都用的是哈希算法。

像 Java 中的 HashSet、Hashtable(注意是小寫的 t)、HashMap 都是基于哈希表的具體實現(xiàn)。其中的 HashMap  就是最典型的代表,不僅面試官經(jīng)常問,工作中的使用頻率也非常的高。

大家想一下,如果沒有哈希表,但又需要這樣一個數(shù)據(jù)結(jié)構(gòu),它里面存放的數(shù)據(jù)是不允許重復(fù)的,該怎么辦呢?

要不使用 equals() 方法進(jìn)行逐個比較?這種方案當(dāng)然是可行的。但如果數(shù)據(jù)量特別特別大,采用 equals()  方法進(jìn)行逐個對比的效率肯定很低很低,最好的解決方案就是哈希表。

拿 HashMap 來說吧。當(dāng)我們要在它里面添加對象時,先調(diào)用這個對象的 hashCode() 方法,得到對應(yīng)的哈希值,然后將哈希值和對象一起放到  HashMap 中。當(dāng)我們要再添加一個新的對象時:

  • 獲取對象的哈希值;

  • 和之前已經(jīng)存在的哈希值進(jìn)行比較,如果不相等,直接存進(jìn)去;

  • 如果有相等的,再調(diào)用 equals() 方法進(jìn)行對象之間的比較,如果相等,不存了;

  • 如果不等,說明哈希沖突了,增加一個鏈表,存放新的對象;

  • 如果鏈表的長度大于 8,轉(zhuǎn)為紅黑樹來處理。

就這么一套下來,調(diào)用 equals()  方法的頻率就大大降低了。也就是說,只要哈希算法足夠的高效,把發(fā)生哈希沖突的頻率降到最低,哈希表的效率就特別的高。

來看一下 HashMap 的哈希算法:

static final int hash(Object key) {     int h;     return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16); }

先調(diào)用對象的 hashCode() 方法,然后對該值進(jìn)行右移運(yùn)算,然后再進(jìn)行異或運(yùn)算。

通常來說,String 會用來作為 HashMap 的鍵進(jìn)行哈希運(yùn)算,因此我們再來看一下 String 的 hashCode() 方法:

public int hashCode() {     int h = hash;     if (h == 0 && value.length > 0) {         hash = h = isLatin1() ? StringLatin1.hashCode(value)                 : StringUTF16.hashCode(value);     }     return h; } public static int hashCode(byte[] value) {     int h = 0;     int length = value.length >> 1;     for (int i = 0; i < length; i++) {         h = 31 * h + getChar(value, i);     }     return h; }

可想而知,經(jīng)過這么一系列復(fù)雜的運(yùn)算,再加上 JDK 作者這種大師級別的設(shè)計,哈希沖突的概率我相信已經(jīng)降到了最低。

當(dāng)然了,從理論上來說,對于兩個不同對象,它們通過 hashCode() 方法計算后的值可能相同。因此,不能使用 hashCode()  方法來判斷兩個對象是否相等,必須得通過 equals() 方法。

也就是說:

  • 如果兩個對象調(diào)用 equals() 方法得到的結(jié)果為 true,調(diào)用 hashCode() 方法得到的結(jié)果必定相等;

  • 如果兩個對象調(diào)用 hashCode() 方法得到的結(jié)果不相等,調(diào)用 equals() 方法得到的結(jié)果必定為 false;

反之:

  • 如果兩個對象調(diào)用 equals() 方法得到的結(jié)果為 false,調(diào)用 hashCode() 方法得到的結(jié)果不一定不相等;

  • 如果兩個對象調(diào)用 hashCode() 方法得到的結(jié)果相等,調(diào)用 equals() 方法得到的結(jié)果不一定為 true;

來看下面這段代碼。

public class Test {     public static void main(String[] args) {         Student s1 = new Student(18, "張三");         Map scores = new HashMap<>();         scores.put(s1, 98);         System.out.println(scores.get(new Student(18, "張三")));     } }  class Student {     private int age;     private String name;       public Student(int age, String name) {          this.age = age;          this.name = name;      }       @Override      public boolean equals(Object o) {          Student student = (Student) o;          return age == student.age &&                  Objects.equals(name, student.name);      }  }

我們重寫了 Student 類的 equals() 方法,如果兩個學(xué)生的年紀(jì)和姓名相同,我們就認(rèn)為是同一個學(xué)生,雖然很離譜,但我們就是這么草率。

在 main() 方法中,18 歲的張三考試得了 98 分,很不錯的成績,我們把張三和成績放到了 HashMap 中,然后準(zhǔn)備輸出張三的成績:

null

很不巧,結(jié)果為 null,而不是預(yù)期當(dāng)中的 98。這是為什么呢?

原因就在于重寫 equals() 方法的時候沒有重寫 hashCode() 方法。默認(rèn)情況下,hashCode()  方法是一個本地方法,會返回對象的存儲地址,顯然 put() 中的 s1 和 get() 中的 new Student(18, "張三")  是兩個對象,它們的存儲地址肯定是不同的。

HashMap 的 get() 方法會調(diào)用 hash(key.hashCode()) 計算對象的哈希值,雖然兩個不同的 hashCode() 結(jié)果經(jīng)過  hash() 方法計算后有可能得到相同的結(jié)果,但這種概率微乎其微,所以就導(dǎo)致 scores.get(new Student(18, "張三"))  無法得到預(yù)期的值 18。

怎么解決這個問題呢?很簡單,重寫 hashCode() 方法。

@Override public int hashCode() {     return Objects.hash(age, name); }

Objects 類的 hash() 方法可以針對不同數(shù)量的參數(shù)生成新的 hashCode() 值。

public static int hashCode(Object a[]) {  if (a == null)      return 0;   int result = 1;   for (Object element : a)      result = 31 * result + (element == null ? 0 : element.hashCode());   return result; }

代碼似乎很簡單,歸納出的數(shù)學(xué)公式如下所示(n 為字符串長度)。

如何Java中的hashCode()方法

注意:31 是個奇質(zhì)數(shù),不大不小,一般質(zhì)數(shù)都非常適合哈希計算,偶數(shù)相當(dāng)于移位運(yùn)算,容易溢出,造成數(shù)據(jù)信息丟失。

這就意味著年紀(jì)和姓名相同的情況下,會得到相同的哈希值。scores.get(new Student(18, "張三")) 就會返回 98  的預(yù)期值了。

《Java 編程思想》這本圣經(jīng)中有一段話,對 hashCode() 方法進(jìn)行了一段描述。

設(shè)計 hashCode() 時最重要的因素就是:無論何時,對同一個對象調(diào)用 hashCode() 都應(yīng)該生成同樣的值。如果在將一個對象用 put()  方法添加進(jìn) HashMap 時產(chǎn)生一個 hashCode() 值,而用 get() 方法取出時卻產(chǎn)生了另外一個 hashCode()  值,那么就無法重新取得該對象了。所以,如果你的 hashCode() 方法依賴于對象中易變的數(shù)據(jù),用戶就要當(dāng)心了,因為此數(shù)據(jù)發(fā)生變化時,hashCode()  就會生成一個不同的哈希值,相當(dāng)于產(chǎn)生了一個不同的鍵。

也就是說,如果在重寫 hashCode() 和 equals()  方法時,對象中某個字段容易發(fā)生改變,那么最好舍棄這些字段,以免產(chǎn)生不可預(yù)期的結(jié)果。

好。有了上面這些內(nèi)容作為基礎(chǔ)后,我們回頭再來看看本地方法 hashCode() 的 C++ 源碼。

static inline intptr_t get_next_hash(Thread* current, oop obj) {   intptr_t value = 0;   if (hashCode == 0) {     // This form uses global Park-Miller 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 addr_bits = cast_from_oop(obj) >> 3;     value = addr_bits ^ (addr_bits >> 5) ^ GVars.stw_random;   } else if (hashCode == 2) {     value = 1;            // for sensitivity testing   } else if (hashCode == 3) {     value = ++GVars.hc_sequence;   } else if (hashCode == 4) {     value = cast_from_oop(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 = current->_hashStateX;     t ^= (t << 11);     current->_hashStateX = current->_hashStateY;     current->_hashStateY = current->_hashStateZ;     current->_hashStateZ = current->_hashStateW;     unsigned v = current->_hashStateW;     v = (v ^ (v >> 19)) ^ (t ^ (t >> 8));     current->_hashStateW = v;     value = v;   }    value &= markWord::hash_mask;   if (value == 0) value = 0xBAD;   assert(value != markWord::no_hash, "invariant");   return value; }

如果沒有 C++ 基礎(chǔ)的話,不用細(xì)致去看每一行代碼,我們只通過表面去了解一下 get_next_hash() 這個方法就行。其中的 hashCode  變量是 JVM 啟動時的一個全局參數(shù),可以通過它來切換哈希值的生成策略。

  • hashCode==0,調(diào)用操作系統(tǒng) OS 的 random() 方法返回隨機(jī)數(shù)。

  • hashCode == 1,在  STW(stop-the-world)操作中,這種策略通常用于同步方案中。利用對象地址進(jìn)行計算,使用不經(jīng)常更新的隨機(jī)數(shù)(GVars.stw_random)參與其中。

  • hashCode == 2,使用返回 1,用于某些情況下的測試。

  • hashCode == 3,從 0 開始計算哈希值,不是線程安全的,多個線程可能會得到相同的哈希值。

  • hashCode == 4,與創(chuàng)建對象的內(nèi)存位置有關(guān),原樣輸出。

  • hashCode == 5,默認(rèn)值,支持多線程,使用了 Marsaglia 的 xor-shift 算法產(chǎn)生偽隨機(jī)數(shù)。所謂的 xor-shift  算法,簡單來說,看起來就是一個移位寄存器,每次移入的位由寄存器中若干位取異或生成。所謂的偽隨機(jī)數(shù),不是完全隨機(jī)的,但是真隨機(jī)生成比較困難,所以只要能通過一定的隨機(jī)數(shù)統(tǒng)計檢測,就可以當(dāng)作真隨機(jī)數(shù)來使用。

至于更深層次的挖掘,涉及到數(shù)學(xué)知識和物理知識,就不展開了。畢竟菜是原罪。

到此,相信大家對“如何Java中的hashCode()方法”有了更深的了解,不妨來實際操作一番吧!這里是創(chuàng)新互聯(lián)網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!


標(biāo)題名稱:如何Java中的hashCode()方法
分享網(wǎng)址:http://weahome.cn/article/pjcgih.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部