Java提高篇(二六)-----hashCode


6608733_100801001323_2_thumb2

      在前面三篇博文中LZ講解了(HashMapHashSetHashTable),在其中LZ不斷地講解他們的put和get方法,在這兩個方法中計算key的hashCode應該是最重要也是最精華的部分,所以下面LZ揭開hashCode的“神秘”面紗。

hashCode的作用

      要想了解一個方法的內在原理,我們首先需要明白它是干什么的,也就是這個方法的作用。在講解數組時(java提高篇(十八)------數組),我們提到數組是java中效率最高的數據結構,但是“最高”是有前提的。第一我們需要知道所查詢數據的所在位置。第二:如果我們進行迭代查找時,數據量一定要小,對於大數據量而言一般推薦集合。

      在Java集合中有兩類,一類是List,一類是Set他們之間的區別就在於List集合中的元素師有序的,且可以重復,而Set集合中元素是無序不可重復的。對於List好處理,但是對於Set而言我們要如何來保證元素不重復呢?通過迭代來equals()是否相等。數據量小還可以接受,當我們的數據量大的時候效率可想而知(當然我們可以利用算法進行優化)。比如我們向HashSet插入1000數據,難道我們真的要迭代1000次,調用1000次equals()方法嗎?hashCode提供了解決方案。怎么實現?我們先看hashCode的源碼(Object)。

public native int hashCode();

      它是一個本地方法,它的實現與本地機器有關,這里我們暫且認為他返回的是對象存儲的物理位置(實際上不是,這里寫是便於理解)。當我們向一個集合中添加某個元素,集合會首先調用hashCode方法,這樣就可以直接定位它所存儲的位置,若該處沒有其他元素,則直接保存。若該處已經有元素存在,就調用equals方法來匹配這兩個元素是否相同,相同則不存,不同則散列到其他位置(具體情況請參考(Java提高篇()-----HashMap))。這樣處理,當我們存入大量元素時就可以大大減少調用equals()方法的次數,極大地提高了效率。

      所以hashCode在上面扮演的角色為尋域(尋找某個對象在集合中區域位置)。hashCode可以將集合分成若干個區域,每個對象都可以計算出他們的hash碼,可以將hash碼分組,每個分組對應着某個存儲區域,根據一個對象的hash碼就可以確定該對象所存儲區域,這樣就大大減少查詢匹配元素的數量,提高了查詢效率。

hashCode對於一個對象的重要性

      hashCode重要么?不重要,對於List集合、數組而言,他就是一個累贅,但是對於HashMap、HashSet、HashTable而言,它變得異常重要。所以在使用HashMap、HashSet、HashTable時一定要注意hashCode。對於一個對象而言,其hashCode過程就是一個簡單的Hash算法的實現,其實現過程對你實現對象的存取過程起到非常重要的作用。

      在前面LZ提到了HashMap和HashTable兩種數據結構,雖然他們存在若干個區別,但是他們的實現原理是相同的,這里我以HashTable為例闡述hashCode對於一個對象的重要性。

      一個對象勢必會存在若干個屬性,如何選擇屬性來進行散列考驗着一個人的設計能力。如果我們將所有屬性進行散列,這必定會是一個糟糕的設計,因為對象的hashCode方法無時無刻不是在被調用,如果太多的屬性參與散列,那么需要的操作數時間將會大大增加,這將嚴重影響程序的性能。但是如果較少屬相參與散列,散列的多樣性會削弱,會產生大量的散列“沖突”,除了不能夠很好的利用空間外,在某種程度也會影響對象的查詢效率。其實這兩者是一個矛盾體,散列的多樣性會帶來性能的降低。

      那么如何對對象的hashCode進行設計,LZ也沒有經驗。從網上查到了這樣一種解決方案:設置一個緩存標識來緩存當前的散列碼,只有當參與散列的對象改變時才會重新計算,否則調用緩存的hashCode,這樣就可以從很大程度上提高性能。

      在HashTable計算某個對象在table[]數組中的索引位置,其代碼如下:

int index = (hash & 0x7FFFFFFF) % tab.length;

      為什么要&0x7FFFFFFF?因為某些對象的hashCode可能會為負值,與0x7FFFFFFF進行與運算可以確保index為一個正數。通過這步我可以直接定位某個對象的位置,所以從理論上來說我們是完全可以利用hashCode直接定位對象的散列表中的位置,但是為什么會存在一個key-value的鍵值對,利用key的hashCode來存入數據而不是直接存放value呢?這就關系HashTable性能問題的最重要的問題:Hash沖突!

      我們知道沖突的產生是由於不同的對象產生了相同的散列碼,假如我們設計對象的散列碼可以確保99.999999999%的不重復,但是有一種絕對且幾乎不可能遇到的沖突你是絕對避免不了的。我們知道hashcode返回的是int,它的值只可能在int范圍內。如果我們存放的數據超過了int的范圍呢?這樣就必定會產生兩個相同的index,這時在index位置處會存儲兩個對象,我們就可以利用key本身來進行判斷。所以具有相索引的對象,在該index位置處存在多個對象,我們必須依靠key的hashCode和key本身來進行區分。

hashCode與equals

      在Java中hashCode的實現總是伴隨着equals,他們是緊密配合的,你要是自己設計了其中一個,就要設計另外一個。當然在多數情況下,這兩個方法是不用我們考慮的,直接使用默認方法就可以幫助我們解決很多問題。但是在有些情況,我們必須要自己動手來實現它,才能確保程序更好的運作。

      對於equals,我們必須遵循如下規則:

      對稱性:如果x.equals(y)返回是“true”,那么y.equals(x)也應該返回是“true”。

      反射性:x.equals(x)必須返回是“true”。

      類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那么z.equals(x)也應該返回是“true”。

      一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重復x.equals(y)多少次,返回都是“true”。

      任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同類型的對象)永遠返回是“false”。

      對於hashCode,我們應該遵循如下規則:

      1. 在一個應用程序執行期間,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法多次,它必須始終如一地返回同一個整數。

      2. 如果兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。

      3. 如果兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的性能。

      至於兩者之間的關聯關系,我們只需要記住如下即可:

      如果x.equals(y)返回“true”,那么x和y的hashCode()必須相等。

      如果x.equals(y)返回“false”,那么x和y的hashCode()有可能相等,也有可能不等。

      理清了上面的關系我們就知道他們兩者是如何配合起來工作的。先看下圖:

2014040701_thumb2

      整個處理流程是:

      1、判斷兩個對象的hashcode是否相等,若不等,則認為兩個對象不等,完畢,若相等,則比較equals。

      2、若兩個對象的equals不等,則可以認為兩個對象不等,否則認為他們相等。

      實例:

public class Person {
    private int age;
    private int sex;    //0:男,1:女
    private String name;
</span><span style="color: #0000ff">private</span> <span style="color: #0000ff">final</span> <span style="color: #0000ff">int</span> PRIME = 37<span style="color: #000000">;

Person(</span><span style="color: #0000ff">int</span> age ,<span style="color: #0000ff">int</span><span style="color: #000000"> sex ,String name){
    </span><span style="color: #0000ff">this</span>.age =<span style="color: #000000"> age;
    </span><span style="color: #0000ff">this</span>.sex =<span style="color: #000000"> sex;
    </span><span style="color: #0000ff">this</span>.name =<span style="color: #000000"> name;
}

</span><span style="color: #008000">/**</span><span style="color: #008000"> 省略getter、setter方法 *</span><span style="color: #008000">*/</span><span style="color: #000000">

@Override
</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">int</span><span style="color: #000000"> hashCode() {
    System.out.println(</span>&quot;調用hashCode方法...........&quot;<span style="color: #000000">);

    </span><span style="color: #0000ff">int</span> hashResult = 1<span style="color: #000000">;
    hashResult </span>= (hashResult + Integer.valueOf(age).hashCode() + Integer.valueOf(sex).hashCode()) *<span style="color: #000000"> PRIME;
    hashResult </span>= PRIME * hashResult + ((name == <span style="color: #0000ff">null</span>) ? 0<span style="color: #000000"> : name.hashCode()); 
    System.out.println(</span>&quot;name:&quot;+name +&quot; hashCode:&quot; +<span style="color: #000000"> hashResult);
    
    </span><span style="color: #0000ff">return</span><span style="color: #000000"> hashResult;
}

</span><span style="color: #008000">/**</span><span style="color: #008000">
 * 重寫hashCode()
 </span><span style="color: #008000">*/</span>
<span style="color: #0000ff">public</span> <span style="color: #0000ff">boolean</span><span style="color: #000000"> equals(Object obj) {
    System.out.println(</span>&quot;調用equals方法...........&quot;<span style="color: #000000">);
    
    </span><span style="color: #0000ff">if</span>(obj == <span style="color: #0000ff">null</span><span style="color: #000000">){
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">;
    }
    </span><span style="color: #0000ff">if</span>(obj.getClass() != <span style="color: #0000ff">this</span><span style="color: #000000">.getClass()){
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">;
    }
    </span><span style="color: #0000ff">if</span>(<span style="color: #0000ff">this</span> ==<span style="color: #000000"> obj){
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">;
    }

    Person person </span>=<span style="color: #000000"> (Person) obj;
    
    </span><span style="color: #0000ff">if</span>(getAge() != person.getAge() || getSex()!=<span style="color: #000000"> person.getSex()){
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">;
    }
    
    </span><span style="color: #0000ff">if</span>(getName() != <span style="color: #0000ff">null</span><span style="color: #000000">){
        </span><span style="color: #0000ff">if</span>(!<span style="color: #000000">getName().equals(person.getName())){
            </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">;
        }
    }
    </span><span style="color: #0000ff">else</span> <span style="color: #0000ff">if</span>(person != <span style="color: #0000ff">null</span><span style="color: #000000">){
        </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">false</span><span style="color: #000000">;
    }
    </span><span style="color: #0000ff">return</span> <span style="color: #0000ff">true</span><span style="color: #000000">;
}

}

      該Bean為一個標准的Java Bean,重新實現了hashCode方法和equals方法。

public class Main extends JPanel {
</span><span style="color: #0000ff">public</span> <span style="color: #0000ff">static</span> <span style="color: #0000ff">void</span><span style="color: #000000"> main(String[] args) {
    Set</span>&lt;Person&gt; set = <span style="color: #0000ff">new</span> HashSet&lt;Person&gt;<span style="color: #000000">();
    
    Person p1 </span>= <span style="color: #0000ff">new</span> Person(11, 1, &quot;張三&quot;<span style="color: #000000">);
    Person p2 </span>= <span style="color: #0000ff">new</span> Person(12, 1, &quot;李四&quot;<span style="color: #000000">);
    Person p3 </span>= <span style="color: #0000ff">new</span> Person(11, 1, &quot;張三&quot;<span style="color: #000000">);
    Person p4 </span>= <span style="color: #0000ff">new</span> Person(11, 1, &quot;李四&quot;<span style="color: #000000">);
    
    </span><span style="color: #008000">//</span><span style="color: #008000">只驗證p1、p3</span>
    System.out.println(&quot;p1 == p3? :&quot; + (p1 ==<span style="color: #000000"> p3));
    System.out.println(</span>&quot;p1.equals(p3)?:&quot;+<span style="color: #000000">p1.equals(p3));
    System.out.println(</span>&quot;-----------------------分割線--------------------------&quot;<span style="color: #000000">);
    set.add(p1);
    set.add(p2);
    set.add(p3);
    set.add(p4);
    System.out.println(</span>&quot;set.size()=&quot;+<span style="color: #000000">set.size());
}

}

       運行結果如下:

2014040702_thumb

      從上圖可以看出,程序調用四次hashCode方法,一次equals方法,其set的長度只有3。add方法運行流程完全符合他們兩者之間的處理流程。

      更多請關注:

      >>>>>>>>>Java提高篇(十三)------equals()

      >>>>>>>>>Java提高篇(二三)------HashMap

      >>>>>>>>>Java提高篇(二四)------HashSet

      >>>>>>>>>Java提高篇(二五)------HashTable


免責聲明!

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



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