前言算法
Object提供給咱們了一個Native的方法「public native int hashCode();」,本文講講Hash是什麼以及HashCode的做用post
Hashthis
先用一張圖看下什麼是Hash
spa
Hash是散列的意思,就是把任意長度的輸入,經過散列算法變換成固定長度的輸出,該輸出就是散列值。關於散列值,有如下幾個關鍵結論:code
一、若是散列表中存在和散列原始輸入K相等的記錄,那麼K一定在f(K)的存儲位置上對象
二、不一樣關鍵字通過散列算法變換後可能獲得同一個散列地址,這種現象稱爲碰撞blog
三、若是兩個Hash值不一樣(前提是同一Hash算法),那麼這兩個Hash值對應的原始輸入一定不一樣內存
HashCodeget
而後講下什麼是HashCode,總結幾個關鍵點:hash
一、HashCode的存在主要是爲了查找的快捷性,HashCode是用來在散列存儲結構中肯定對象的存儲地址的
二、若是兩個對象equals相等,那麼這兩個對象的HashCode必定也相同
三、若是對象的equals方法被重寫,那麼對象的HashCode方法也儘可能重寫
四、若是兩個對象的HashCode相同,不表明兩個對象就相同,只能說明這兩個對象在散列存儲結構中,存放於同一個位置
HashCode有什麼用
回到最關鍵的問題,HashCode有什麼用?不妨舉個例子:
一、假設內存中有0 1 2 3 4 5 6 7 8這8個位置,若是我有個字段叫作ID,那麼我要把這個字段存放在以上8個位置之一,若是不用HashCode而任意存放,那麼當查找時就須要到8個位置中去挨個查找
二、使用HashCode則效率會快不少,把ID的HashCode%8,而後把ID存放在取得餘數的那個位置,而後每次查找該類的時候均可以經過ID的HashCode%8求餘數直接找到存放的位置了
三、若是ID的 HashCode%8算出來的位置上自己已經有數據了怎麼辦?這就取決於算法的實現了,好比ThreadLocal中的作法就是從算出來的位置向後查找第 一個爲空的位置,放置數據;HashMap的作法就是經過鏈式結構連起來。反正,只要保證放的時候和取的時候的算法一致就好了。
四、若是ID的 HashCode%8相等怎麼辦(這種對應的是第三點說的鏈式結構的場景)?這時候就須要定義equals了。先經過HashCode%8來判斷類在哪一 個位置,再經過equals來在這個位置上尋找須要的類。對比兩個類的時候也差很少,先經過HashCode比較,假如HashCode相等再判斷 equals。若是兩個類的HashCode都不相同,那麼這兩個類一定是不一樣的。
舉個實際的例子Set。咱們知道Set裏面的元素是不能夠重複的,那麼如何作到?Set是根據equals()方法來判斷兩個元素是否相等的。比方 說Set裏面已經有1000個元素了,那麼第1001個元素進來的時候,最多可能調用1000次equals方法,若是equals方法寫得複雜,對比的 東西特別多,那麼效率會大大下降。使用HashCode就不同了,比方說HashSet,底層是基於HashMap實現的,先經過HashCode取一 個模,這樣一會兒就固定到某個位置了,若是這個位置上沒有元素,那麼就能夠確定HashSet中一定沒有和新添加的元素equals的元素,就能夠直接存 放了,都不須要比較;若是這個位置上有元素了,逐一比較,比較的時候先比較HashCode,HashCode都不一樣接下去都不用比了,確定不一 樣,HashCode相等,再equals比較,沒有相同的元素就存,有相同的元素就不存。若是原來的Set裏面有相同的元素,只要HashCode的生 成方式定義得好(不重複),無論Set裏面原來有多少元素,只須要執行一次的equals就能夠了。這樣一來,實際調用equals方法的次數大大下降, 提升了效率。
爲何重寫Object的equals(Object obj)方法儘可能要重寫Object的hashCode()方法
咱們在重寫Object的equals(Object obj)方法的時候,應該儘可能重寫hashCode()方法,這是有緣由的,下面詳細解釋下:
1 public class HashCodeClass 2 { 3 private String str0; 4 private double dou0; 5 private int int0; 6 7 public boolean equals(Object obj) 8 { 9 if (obj instanceof HashCodeClass) 10 { 11 HashCodeClass hcc = (HashCodeClass)obj; 12 if (hcc.str0.equals(this.str0) && 13 hcc.dou0 == this.dou0 && 14 hcc.int0 == this.int0) 15 { 16 return true; 17 } 18 return false; 19 } 20 return false; 21 } 22 }
1 public class TestMain 2 { 3 public static void main(String[] args) 4 { 5 System.out.println(new HashCodeClass().hashCode()); 6 System.out.println(new HashCodeClass().hashCode()); 7 System.out.println(new HashCodeClass().hashCode()); 8 System.out.println(new HashCodeClass().hashCode()); 9 System.out.println(new HashCodeClass().hashCode()); 10 System.out.println(new HashCodeClass().hashCode()); 11 } 12 }
打印出來的值是:
1901116749 1807500377 355165777 1414159026 1569228633 778966024
咱們但願兩個HashCodeClass類equals的前提是兩個HashCodeClass的str0、dou0、int0分別相等。OK,那麼這個類不重寫hashCode()方法是有問題的。
如今個人HashCodeClass都沒有賦初值,那麼這6個HashCodeClass應該是所有equals的。若是以HashSet爲 例,HashSet內部的HashMap的table自己的大小是16,那麼6個HashCode對16取模分別爲1三、九、一、二、九、8。第一個放入 table[13]的位置、第二個放入table[9]的位置、第三個放入table[1]的位置。。。可是明明是所有equals的6個 HashCodeClass,怎麼能這麼作呢?HashSet自己要求的就是equals的對象不重複,如今6個equals的對象在集合中卻有5份(因 爲有兩個計算出來的模都是9)。
那麼咱們該怎麼作呢?重寫hashCode方法,根據str0、dou0、int0搞一個算法生成一個儘可能惟一的hashCode,這樣就保證了 str0、dou0、int0都相等的兩個HashCodeClass它們的HashCode是相等的,這就是重寫equals方法必須儘可能要重寫 hashCode方法的緣由。看下JDK中的一些類,都有這麼作:
Integer的
1 public int hashCode() { 2 return value; 3 } 4 5 public boolean equals(Object obj) { 6 if (obj instanceof Integer) { 7 return value == ((Integer)obj).intValue(); 8 } 9 return false; 10 }
String的
1 public int hashCode() { 2 int h = hash; 3 if (h == 0) { 4 int off = offset; 5 char val[] = value; 6 int len = count; 7 8 for (int i = 0; i < len; i++) { 9 h = 31*h + val[off++]; 10 } 11 hash = h; 12 } 13 return h; 14 } 15 16 public boolean equals(Object anObject) { 17 if (this == anObject) { 18 return true; 19 } 20 if (anObject instanceof String) { 21 String anotherString = (String)anObject; 22 int n = count; 23 if (n == anotherString.count) { 24 char v1[] = value; 25 char v2[] = anotherString.value; 26 int i = offset; 27 int j = anotherString.offset; 28 while (n-- != 0) { 29 if (v1[i++] != v2[j++]) 30 return false; 31 } 32 return true; 33 } 34 } 35 return false; 36 }
HashMap中的實體類Entry
public final int hashCode() { return (key==null ? 0 : key.hashCode()) ^ (value==null ? 0 : value.hashCode()); } public final boolean equals(Object o) { if (!(o instanceof Map.Entry)) return false; Map.Entry e = (Map.Entry)o; Object k1 = getKey(); Object k2 = e.getKey(); if (k1 == k2 || (k1 != null && k1.equals(k2))) { Object v1 = getValue(); Object v2 = e.getValue(); if (v1 == v2 || (v1 != null && v1.equals(v2))) return true; } return false; }