來自https://www.cnblogs.com/szlbm/p/5504623.html 前言
Object提供給我們了一個Native的方法“public native int hashCode();”,本文講講Hash是什麼以及HashCode的作用 Hash
先用一張圖看下什麼是Hash
Hash是散列的意思,就是把任意長度的輸入,通過散列演算法變換成固定長度的輸出,該輸出就是散列值。關於散列值,有以下幾個關鍵結論:
1、如果散列表中存在和散列原始輸入K相等的記錄,那麼K必定在f(K)的儲存位置上
2、不同關鍵字經過散列演算法變換後可能得到同一個散列地址,這種現象稱為碰撞
3、如果兩個Hash值不同(前提是同一Hash演算法),那麼這兩個Hash值對應的原始輸入必定不同 HashCode
然後講下什麼是HashCode,總結幾個關鍵點:
1、HashCode的存在主要是為了尋找的快捷性,HashCode是用來在散列儲存結構中確定對象的儲存地址的
2、如果兩個對象equals相等,那麼這兩個對象的HashCode一定也相同
3、如果對象的equals方法被重寫,那麼對象的HashCode方法也盡量重寫
4、如果兩個對象的HashCode相同,不代表兩個對象就相同,只能說明這兩個對象在散列儲存結構中,存放於同一個位置 HashCode有什麼用
回到最關鍵的問題,HashCode有什麼用。不妨舉個例子:
1、假設記憶體中有0 1 2 3 4 5 6 7 8這8個位置,如果我有個欄位叫做ID,那麼我要把這個欄位存放在以上8個位置之一,如果不用HashCode而任意存放,那麼當尋找時就需要到8個位置中去挨個尋找
2、使用HashCode則效率會快很多,把ID的HashCode%8,然後把ID存放在取得餘數的那個位置,然後每次尋找該類的時候都可以通過ID的HashCode%8求餘數直接找到存放的位置了
3、如果ID的 HashCode%8算出來的位置上本身已經有資料了怎麼辦。這就取決於演算法的實現了,比如ThreadLocal中的做法就是從算出來的位置向後尋找第 一個為空白的位置,放置資料;HashMap的做法就是通過鏈式結構連起來。反正,只要保證放的時候和取的時候的演算法一致就行了。
4、如果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()方法,這是有原因的,下面詳細解釋下:
public class HashCodeClass{ private String str0; private double dou0; private int int0; public boolean equals(Object obj) { if (obj instanceof HashCodeClass) { HashCodeClass hcc = (HashCodeClass)obj; if (hcc.str0.equals(this.str0) && hcc.dou0 == this.dou0 && hcc.int0 == this.int0) { return true; } return false; } return false; }}
public class TestMain{ public static void main(String[] args) { System.out.println(new HashCodeClass().hashCode()); System.out.println(new HashCodeClass().hashCode()); System.out.println(new HashCodeClass().hashCode()); System.out.println(new HashCodeClass().hashCode()); System.out.println(new HashCodeClass().hashCode()); System.out.println(new HashCodeClass().hashCode()); }}
列印出來的值是:
1901116749180750037735516577714141590261569228633778966024
我們希望兩個HashCodeClass類equals的前提是兩個HashCodeClass的str0、dou0、int0分別相等。OK,那麼這個類不重寫hashCode()方法是有問題的。
現在我的HashCodeClass都沒有賦初值,那麼這6個HashCodeClass應該是全部equals的。如果以HashSet為 例,HashSet內部的HashMap的table本身的大小是16,那麼6個HashCode對16模數分別為13、9、1、2、9、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的
public int hashCode() { return value; }public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
所以在Integer中,變數的HashCode和變數的值相同
String的
public int hashCode() { int h = hash; if (h == 0) { int off = offset; // char val[] = value; int len = count; for (int i = 0; i < len; i++) { h = 31*h + val[off++]; } hash = h; } return h; }public boolean equals(Object anObject) { //如果是同一個對象,返回true if (this == anObject) { return true; } //如果所比較的對象是String類型 if (anObject instanceof String) { String anotherString = (String)anObject; int n = count; if (n == anotherString.count) { char v1[] = value; char v2[] = anotherString.value; int i = offset; int j = anotherString.offset; while (n-- != 0) { if (v1[i++] != v2[j++]) return false; } return true; } } return false; }
在String中,相同字串的HashCode相同,但是通過不同方式建立的對象使用equals方法相比並不相同。
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; }