講講HashCode的作用


前言

Object提供給我們了一個Native的方法“public native int hashCode();”,本文講講Hash是什么以及HashCode的作用

 

Hash

先用一張圖看下什么是Hash

Hash是散列的意思,就是把任意長度的輸入,通過散列算法變換成固定長度的輸出,該輸出就是散列值。關於散列值,有以下幾個關鍵結論:

  • 如果散列表中存在和散列原始輸入K相等的記錄,那么K必定在f(K)的存儲位置上
  • 不同關鍵字經過散列算法變換后可能得到同一個散列地址,這種現象稱為碰撞
  • 如果兩個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這9個位置,如果我有個字段叫做ID,那么我要把這個字段存放在以上9個位置之一,如果不用HashCode而任意存放,那么當查找時就需要到8個位置中去挨個查找

2、使用HashCode則效率會快很多,把ID的HashCode % 9,然后把ID存放在取得余數的那個位置,然后每次查找該類的時候都可以通過ID的HashCode % 9求余數直接找到存放的位置了

3、如果ID的HashCode % 9算出來的位置上本身已經有數據了怎么辦?這就取決於算法的實現了,比如ThreadLocal中的做法就是從算出來的位置向后查找第一個為空的位置,放置數據;HashMap的做法就是通過鏈式結構連起來。反正,只要保證放的時候和取的時候的算法一致就行了。

4、如果ID的HashCode % 9相等怎么辦(這種對應的是第三點說的鏈式結構的場景)?這時候就需要定義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取模分別為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的

 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;
        }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM