hashcode的理解

============================================================ 
如何理解hashCode的做用:
============================================================ 
以java.lang.Object來理解,JVM每new一個Object,它都會將這個Object丟到一個Hash哈希表中去,這樣的話,下次作Object的比較或者取這個對象的時候,它會根據對象的hashcode再從Hash表中取這個對象。這樣作的目的是提升取對象的效率。具體過程是這樣:
1.new Object(),JVM根據這個對象的Hashcode值,放入到對應的Hash表對應的Key上,若是不一樣的對象確產生了相同的hash值,也就是發生了Hash key相同致使衝突的狀況,那麼就在這個Hash key的地方產生一個鏈表,將全部產生相同hashcode的對象放到這個單鏈表上去,串在一塊兒。
2.比較兩個對象的時候,首先根據他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,那麼就是說他們這兩個對象放在Hash表中的同一個key上,那麼他們必定在這個key上的鏈表上。那麼此時就只能根據Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不一樣的話,確定他們不能equal.

============================================================   
改寫equals時老是要改寫hashCode
============================================================
java.lnag.Object中對hashCode的約定:

   1. 在一個應用程序執行期間,若是一個對象的equals方法作比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法屢次,它必須始終如一地返回同一個整數。
   2. 若是兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
   3. 若是兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不一樣的整數結果。但若是能不一樣,則可能提升散列表的性能。

   
有一個概念要牢記,兩個相等對象的equals方法必定爲true, 但兩個hashcode相等的對象不必定是相等的對象。

因此hashcode相等只能保證兩個對象在一個HASH表裏的同一條HASH鏈上,繼而經過equals方法才能肯定是否是同一對象,若是結果爲true, 則認爲是同一對象在插入,不然認爲是不一樣對象繼續插入。

Object的代碼:java

public String toString () {
    return this.getClass().getName() + "@" + Integer.toHexString(this.hashCode());
}

public boolean equals (Object o) {
    return this == o;
}

/**
 * Answers an integer hash code for the receiver. Any two
 * objects which answer <code>true</code> when passed to
 * <code>.equals</code> must answer the same value for this
 * method.
 *
 * @author        OTI
 * @version        initial
 *
 * @return        int
 *                    the receiver's hash.
 *
 * @see            #equals
 */
public native int hashCode();

從上面咱們能夠看到是否極可能Object.hashCode就是表明內存地址。下面咱們來證實hashcode是否是真的就是Object的內存地址呢?實際上,hashcode根本不能表明object的內存地址。
-----------------------------------------
Object.hashCode不能夠表明內存地址
----------------------------------------程序員

package com.tools;

import java.util.ArrayList;

/**
 * 此方法的做用是證實 java.lang.Object的hashcode 不是表明 對象所在內存地址。
 * 我產生了10000個對象,這10000個對象在內存中是不一樣的地址,可是實際上這10000個對象
 * 的hashcode的是徹底可能相同的
 */
public class HashCodeMeaning {
    public static void main(String[] args) {
        ArrayList list =  new ArrayList();
        int numberExist=0;
        
        //證實hashcode的值不是內存地址
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj.toString())) {
                System.out.println(obj.toString() +"  exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj.toString());
            }
        }
        
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
        
        //證實內存地址是不一樣的。
        numberExist=0;
        list.clear();
        for (int i = 0; i < 10000; i++) {
            Object obj=new Object();
            if (list.contains(obj)) {
                System.out.println(obj +"  exists in the list. "+ i);
                numberExist++;
            }
            else {
                list.add(obj);
            }
        }
        
        System.out.println("repetition number:"+numberExist);
        System.out.println("list size:"+list.size());
    }
}

==============================
看HashTable的源代碼很是有用:
==============================   

==============================   
HashCode方法使用簡介      
==============================   
Hash表數據結構常識: 
1、哈希表基於數組。 
2、缺點:基於數組的,數組建立後難以擴展。某些哈希表被基本填滿時,性能降低得很是嚴重。 
3、沒有一種簡便得方法能夠以任何一種順序遍歷表中數據項。 
4、若是不須要有序遍歷數據,而且能夠提早預測數據量的大小,那麼哈希表在速度和易用性方面是無與倫比的。 
  
1、爲何HashCode對於對象是如此的重要: 
一個對象的HashCode就是一個簡單的Hash算法的實現,雖然它和那些真正的複雜的Hash算法相比還不能叫真正的算法,它如何實現它,不只僅是程序員的編程水平問題, 
而是關係到你的對象在存取是性能的很是重要的關係.有可能,不一樣的HashCode可能會使你的對象存取產生,成百上千倍的性能差異. 
先來看一下,在JAVA中兩個重要的數據結構:HashMap和Hashtable,雖然它們有很大的區別,如繼承關係不一樣,對value的約束條件(是否容許null)不一樣,以及線程安全性等有着特定的區別,但從實現原理上來講,它們是一致的.因此,咱們只以Hashtable來講明: 
在java中,存取數據的性能,通常來講固然是首推數組,可是在數據量稍大的容器選擇中,Hashtable將有比數據性能更高的查詢速度.具體緣由看下面的內容. 
Hashtable在存儲數據時,通常先將該對象的HashCode和0x7FFFFFFF作與操做,由於一個對象的HashCode能夠爲負數,這樣操做後能夠保證它爲一個正整數.而後以Hashtable的長度取模,獲得該對象在Hashtable中的索引. 
index = (o.hashCode() & 0x7FFFFFFF)%hs.length; 
這個對象就會直接放在Hashtable的每index位置,對於寫入,這和數據同樣,把一個對象放在其中的第index位置,但若是是查詢,通過一樣的算法,Hashtable能夠直接從第index取得這個對象,而數組卻要作循環比較.因此對於數據量稍大時,Hashtable的查詢比數據具備更高的性能. 
既然一個對象能夠根據HashCode直接定位它在Hashtable中的位置,那麼爲何Hashtable還要用key來作映射呢?這就是關係Hashtable性能問題的最重要的問題:Hash衝突. 
常見的Hash衝突是不一樣對象最終產生了相同的索引,而一種很是甚至絕對少見的Hash衝突是,若是一組對象的個數大過了int範圍,而HashCode的長度只能在int範圍中,因此確定要有同一組的元素有相同的HashCode,這樣不管如何他們都會有相同的索引.固然這種極端的狀況是極少見的,能夠暫不考慮,可是對於同的HashCode通過取模,則會產中相同的索引,或者不一樣的對象卻具備相同的HashCode,固然具備相同的索引. 
因此對於索引相同的對象,在該index位置存放了多個值,這些值要想能正確區分,就要依靠key來識別. 
事實上一個設計各好的HashTable,通常來講會比較平均地分佈每一個元素,由於Hashtable的長度老是比實際元素的個數按必定比例進行自增(裝填因子通常爲0.75)左右,這樣大多數的索引位置只有一個對象,而不多的位置會有幾個元素.因此Hashtable中的每一個位置存放的是一個鏈表,對於只有一個對象是位置,鏈表只有一個首節點(Entry),Entry的next爲null.而後有hashCode,key,value屬性保存了該位置的對象的HashCode,key和value(對象自己),若是有相同索引的對象進來則會進入鏈表的下一個節點.若是同一個索引中有多個對象,根據HashCode和key能夠在該鏈表中找到一個和查詢的key相匹配的對象. 
從上面我看能夠看到,對於HashMap和Hashtable的存取性能有重大影響的首先是應該使該數據結構中的元素儘可能大可能具備不一樣的HashCode,雖然這並不能保證不一樣的HashCode產生不一樣的index,但相同的HashCode必定產生相同的index,從而影響產生Hash衝突. 
對於一個象,若是具備不少屬性,把全部屬性都參與散列,顯然是一種笨拙的設計.由於對象的HashCode()方法幾乎無所不在地被自動調用,如equals比較,若是太多的對象參與了散列. 
那麼須要的操做常數時間將會增長很大.因此,挑選哪些屬性參與散列絕對是一個編程水平的問題. 
從實現來講,通常的HashCode方法會這樣: 
return Attribute1.HashCode() Attribute1.HashCode()..[ super.HashCode()],咱們知道,每次調用這個方法,都要從新對方法內的參與散列的對象從新計算一次它們的HashCode的運算,若是一個對象的屬性沒有改變,仍然要每次都進行計算,因此若是設置一個標記來緩存當前的散列碼,只要當參與散列的對象改變時才從新計算,不然調用緩存的hashCode,這能夠從很大程度上提升性能. 
默認的實現是將對象內部地址轉化爲整數做爲HashCode,這固然能保證每一個對象具備不一樣的HasCode,由於不一樣的對象內部地址確定不一樣(廢話),但java語言並不能讓程序員獲取對象內部地址,因此,讓每一個對象產生不一樣的HashCode有着不少可研究的技術. 
若是從多個屬性中採樣出能具備平均分佈的hashCode的屬性,這是一個性能和多樣性相矛盾的地方,若是全部屬性都參與散列,固然hashCode的多樣性將大大提升,但犧牲了性能,而若是隻能少許的屬性採樣散列,極端狀況會產生大量的散列衝突,如對"人"的屬性中,若是用性別而不是姓名或出生日期,那將只有兩個或幾個可選的hashcode值,將產生一半以上的散列衝突.因此若是可能的條件下,專門產生一個序列用來生成HashCode將是一個好的選擇(固然產生序列的性能要比全部屬性參與散列的性能高的狀況下才行,不然還不如直接用全部屬性散列). 
如何對HashCode的性能和多樣性求得一個平衡,能夠參考相關算法設計的書,其實並不必定要求很是的優秀,只要能盡最大可能減小散列值的彙集.重要的是咱們應該記得HashCode對於咱們的程序性能有着重要的影響,在程序設計時應該時時加以注意. 
請記住:若是你想有效的使用HashMap,你就必須重寫在其的HashCode()。 
還有兩條重寫HashCode()的原則: 
沒必要對每一個不一樣的對象都產生一個惟一的hashcode,只要你的HashCode方法使get()可以獲得put()放進去的內容就能夠了。即「不爲一原則」。生成hashcode的算法儘可能使hashcode的值分散一些, 不要不少hashcode都集中在一個範圍內,這樣有利於提升HashMap的性能。即「分散原則」。至於第二條原則的具體緣由,有興趣者能夠參考Bruce Eckel的《Thinking in Java》,
在那裏有對HashMap內部實現原理的介紹,這裏就不贅述了。 
掌握了這兩條原則,你就可以用好HashMap編寫本身的程序了。不知道你們注意沒有, java.lang.Object中提供的三個方法:clone(),equals()和hashCode()雖然很典型,但在不少狀況下都不可以適用,它們只是簡單的由對象的地址得出結果。這就須要咱們在本身的程序中重寫它們,其實java類庫中也重寫了千千萬萬個這樣的方法。利用面向對象的多態性——覆蓋,Java的設計者很優雅的構建了Java的結構,也更加體現了Java是一門純OOP語言的特性。 
Java提供的Collection和Map的功能是十分強大的,它們可以使你的程序實現方式更爲靈活,執行效率更高。但願本文可以對你們更好的使用HashMap有所幫助。 
  
hashcode理論與實踐: 
有效和正肯定義hashCode()和equals() 
每一個Java對象都有hashCode()和 equals()方法。許多類忽略(Override)這些方法的缺省實施,以在對象實例之間提供更深層次的語義可比性。 
雖然Java語言不直接支持關聯數組。可使用任何對象做爲一個索引的數組 -- 但在根Object類中使用hashCode()方法明確表示指望普遍使用HashMap(及其前輩Hashtable)。理想狀況下基於散列的容器提供有效插入和有效檢索;直接在對象模式中支持散列能夠促進基於散列的容器的開發和使用。 
定義對象的相等性 
Object類有兩種方法來推斷對象的標識:equals()和hashCode()。通常來講,若是您忽略了其中一種,您必須同時忽略這兩種,由於二者之間有必須維持的相當重要的關係。特殊狀況是根據equals() 方法,若是兩個對象是相等的,它們必須有相同的hashCode()值(儘管這一般不是真的)。 
特定類的equals()的語義在Implementer的左側定義;定義對特定類來講equals()意味着什麼是其設計工做的一部分。Object提供的缺省實施簡單引用下面等式: 
public boolean equals(Object obj) { return (this == obj); } 
在這種缺省實施狀況下,只有它們引用真正同一個對象時這兩個引用纔是相等的。一樣,Object提供的hashCode()的缺省實施經過將對象的內存地址對映於一個整數值來生成。因爲在某些架構上,地址空間大於int值的範圍,兩個不一樣的對象有相同的hashCode()是可能的。若是您忽略了hashCode(),您仍舊可使用System.identityHashCode()方法來接入這類缺省值。 
忽略 equals() -- 簡單實例 
缺省狀況下,equals()和hashCode()基於標識的實施是合理的,但對於某些類來講,它們但願放寬等式的定義。例如,Integer類定義equals() 與下面相似: 
public boolean equals(Object obj) { 
return (obj instanceof Integer 
%26amp;%26amp; intValue() == ((Integer) obj).intValue()); 

在這個定義中,只有在包含相同的整數值的狀況下這兩個Integer對象是相等的。結合將不可修改的Integer,這使得使用Integer做爲HashMap中的關鍵字是切實可行的。這種基於值的Equal方法能夠由Java類庫中的全部原始封裝類使用,如Integer、Float、Character和Boolean以及String(若是兩個String對象包含相同順序的字符,那它們是相等的)。因爲這些類都是不可修改的而且能夠實施hashCode()和equals(),它們均可以作爲很好的散列關鍵字。 
爲何忽略 equals()和hashCode()? 
若是Integer不忽略equals() 和 hashCode()狀況又將如何?若是咱們從未在HashMap或其它基於散列的集合中使用Integer做爲關鍵字的話,什麼也不會發生。可是,若是咱們在HashMap中使用這類Integer對象做爲關鍵字,咱們將不可以可靠地檢索相關的值,除非咱們在get()調用中使用與put()調用中極其相似的Integer實例。這要求確保在咱們的整個程序中,只能使用對應於特定整數值的Integer對象的一個實例。不用說,這種方法極不方便並且錯誤頻頻。 
Object的interface contract要求若是根據 equals()兩個對象是相等的,那麼它們必須有相同的hashCode()值。當其識別能力整個包含在equals()中時,爲何咱們的根對象類須要hashCode()?hashCode()方法純粹用於提升效率。Java平臺設計人員預計到了典型Java應用程序中基於散列的集合類(Collection Class)的重要性--如Hashtable、HashMap和HashSet,而且使用equals()與許多對象進行比較在計算方面很是昂貴。使全部Java對象都可以支持 hashCode()並結合使用基於散列的集合,能夠實現有效的存儲和檢索。 
  
實施equals()和hashCode()的需求 
實施equals()和 hashCode()有一些限制,Object文件中列舉出了這些限制。特別是equals()方法必須顯示如下屬性: 
Symmetry:兩個引用,a和 b,a.equals(b) if and only if b.equals(a) 
Reflexivity:全部非空引用, a.equals(a) 
Transitivity:If a.equals(b) and b.equals(c), then a.equals(c) 
Consistency with hashCode():兩個相等的對象必須有相同的hashCode()值 
Object的規範中並無明確要求equals()和 hashCode() 必須一致 -- 它們的結果在隨後的調用中將是相同的,假設「不改變對象相等性比較中使用的任何信息。」這聽起來象「計算的結果將不改變,除非實際狀況如此。」這一模糊聲明一般解釋爲相等性和散列值計算應是對象的可肯定性功能,而不是其它。 
對象相等性意味着什麼? 
人們很容易知足Object類規範對equals() 和 hashCode() 的要求。決定是否和如何忽略equals()除了判斷之外,還要求其它。在簡單的不可修值類中,如Integer(事實上是幾乎全部不可修改的類),選擇至關明顯 -- 相等性應基於基本對象狀態的相等性。在Integer狀況下,對象的惟一狀態是基本的整數值。 
對於可修改對象來講,答案並不老是如此清楚。equals() 和hashCode() 是否應基於對象的標識(象缺省實施)或對象的狀態(象Integer和String)?沒有簡單的答案 -- 它取決於類的計劃使用。對於象List和Map這樣的容器來講,人們對此爭論不已。Java類庫中的大多數類,包括容器類,錯誤出如今根據對象狀態來提供equals()和hashCode()實施。 
若是對象的hashCode()值能夠基於其狀態進行更改,那麼當使用這類對象做爲基於散列的集合中的關鍵字時咱們必須注意,確保當它們用於做爲散列關鍵字時,咱們並不容許更改它們的狀態。全部基於散列的集合假設,當對象的散列值用於做爲集合中的關鍵字時它不會改變。若是當關鍵字在集合中時它的散列代碼被更改,那麼將產生一些不可預測和容易混淆的結果。實踐過程當中這一般不是問題 -- 咱們並不常用象List這樣的可修改對象作爲HashMap中的關鍵字。 
一個簡單的可修改類的例子是Point,它根據狀態來定義equals()和hashCode()。若是兩個Point 對象引用相同的(x, y)座標,Point的散列值來源於x和y座標值的IEEE 754-bit表示,那麼它們是相等的。 
對於比較複雜的類來講,equals()和hashCode()的行爲可能甚至受到superclass或interface的影響。例如,List接口要求若是而且只有另外一個對象是List,並且它們有相同順序的相同的Elements(由Element上的Object.equals() 定義),List對象等於另外一個對象。hashCode()的需求更特殊--list的hashCode()值必須符合如下計算: 
hashCode = 1; 
Iterator i = list.iterator(); 
while (i.hasNext()) { 
Object obj = i.next(); 
hashCode = 31*hashCode + (obj==null ? 0 : obj.hashCode()); 

不只僅散列值取決於list的內容,並且還規定告終合各個Element的散列值的特殊算法。(String類規定相似的算法用於計算String的散列值。) 
編寫本身的equals()和hashCode()方法 
忽略缺省的equals()方法比較簡單,但若是不違反對稱(Symmetry)或傳遞性(Transitivity)需求,忽略已經忽略的equals() 方法極其棘手。當忽略equals()時,您應該老是在equals()中包括一些Javadoc註釋,以幫助那些但願可以正確擴展您的類的用戶。 
做爲一個簡單的例子,考慮如下類: 
class A { 
final B someNonNullField; 
C someOtherField; 
int someNonStateField; 

咱們應如何編寫該類的equals()的方法?這種方法適用於許多狀況: 
public boolean equals(Object other) { 
// Not strictly necessary, but often a good optimization 
if (this == other) 
return true; 
if (!(other instanceof A)) 
return false; 
  
A otherA = (A) other; 
return 
(someNonNullField.equals(otherA.someNonNullField)) 
%26amp;%26amp; ((someOtherField == null) 
? otherA.someOtherField == null 
: someOtherField.equals(otherA.someOtherField))); 

如今咱們定義了equals(),咱們必須以統一的方法來定義hashCode()。一種統一但並不老是有效的定義hashCode()的方法以下: 
public int hashCode() { return 0; } 
這種方法將生成大量的條目並顯著下降HashMaps的性能,但它符合規範。一個更合理的hashCode()實施應該是這樣: 
public int hashCode() { 
int hash = 1; 
hash = hash * 31 + someNonNullField.hashCode(); 
hash = hash * 31 
+ (someOtherField == null ? 0 : someOtherField.hashCode()); 
return hash; 

注意:這兩種實施都下降了類狀態字段的equals()或hashCode()方法必定比例的計算能力。根據您使用的類,您可能但願下降superclass的equals()或hashCode()功能一部分計算能力。對於原始字段來講,在相關的封裝類中有helper功能,能夠幫助建立散列值,如Float.floatToIntBits。 
編寫一個完美的equals()方法是不現實的。一般,當擴展一個自身忽略了equals()的instantiable類時,忽略equals()是不切實際的,並且編寫將被忽略的equals()方法(如在抽象類中)不一樣於爲具體類編寫equals()方法。關於實例以及說明的更詳細信息請參閱Effective Java Programming Language Guide, Item 7 (參考資料) 。 
有待改進? 
將散列法構建到Java類庫的根對象類中是一種很是明智的設計折衷方法 -- 它使使用基於散列的容器變得如此簡單和高效。可是,人們對Java類庫中的散列算法和對象相等性的方法和實施提出了許多批評。java.util中基於散列的容器很是方便和簡便易用,但可能不適用於須要很是高性能的應用程序。雖然其中大部分將不會改變,但當您設計嚴重依賴於基於散列的容器效率的應用程序時必須考慮這些因素,它們包括: 
過小的散列範圍。使用int而不是long做爲hashCode()的返回類型增長了散列衝突的概率。 
糟糕的散列值分配。短strings和小型integers的散列值是它們本身的小整數,接近於其它「鄰近」對象的散列值。一個循規導矩(Well-behaved)的散列函數將在該散列範圍內更均勻地分配散列值。 
無定義的散列操做。雖然某些類,如String和List,定義了將其Element的散列值結合到一個散列值中使用的散列算法,但語言規範不定義將多個對象的散列值結合到新散列值中的任何批准的方法。咱們在前面編寫本身的equals()和hashCode()方法中討論的List、String或實例類A使用的訣竅都很簡單,但算術上還遠遠不夠完美。類庫不提供任何散列算法的方便實施,它能夠簡化更先進的hashCode()實施的建立。 
當擴展已經忽略了equals()的 instantiable類時很難編寫equals()。當擴展已經忽略了equals()的 instantiable類時,定義equals()的「顯而易見的」方式都不能知足equals()方法的對稱或傳遞性需求。這意味着當忽略equals()時,您必須瞭解您正在擴展的類的結構和實施詳細信息,甚至須要暴露基本類中的機密字段,它違反了面向對象的設計的原則。 
結束語 
經過統必定義equals()和hashCode(),您能夠提高類做爲基於散列的集合中的關鍵字的使用性。有兩種方法來定義對象的相等性和散列值:基於標識,它是Object提供的缺省方法;基於狀態,它要求忽略equals()和hashCode()。當對象的狀態更改時若是對象的散列值發生變化,確信當狀態做爲散列關鍵字使用時您不容許更更改其狀態。 




解析Java對象的equals()和hashCode()的使用:

在Java語言中,equals()和hashCode()兩個函數的使用是緊密配合的,你要是本身設計其中一個,就要設計另一個。在多數狀況 下,這兩個函數是不用考慮的,直接使用它們的默認設計就能夠了。可是在一些狀況下,這兩個函數最好是本身設計,才能確保整個程序的正常運行。最多見的是當 一個對象被加入收集對象(collection object)時,這兩個函數必須本身設計。更細化的定義是:若是你想將一個對象A放入另外一個收集對象B裏,或者使用這個對象A爲查找一個元對象在收集對 象B裏位置的鑰匙,並支持是否容納,刪除收集對象B裏的元對象這樣的操做,那麼,equals()和hashCode()函數必須開發者本身定義。其餘情 況下,這兩個函數是不須要定義的。

equals(): 
它是用於進行兩個對象的比較的,是對象內容的比較,固然也能用於進行對象參閱值的比較。什麼是對象參閱值的比較?就是兩個參閱變量的值得比較,咱們 都知道參閱變量的值其實就是一個數字,這個數字能夠當作是鑑別不一樣對象的代號。兩個對象參閱值的比較,就是兩個數字的比較,兩個代號的比較。這種比較是默 認的對象比較方式,在Object這個對象中,這種方式就已經設計好了。因此你也不用本身來重寫,浪費沒必要要的時間。

對象內容的比較纔是設計equals()的真正目的,Java語言對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():
這 個函數返回的就是一個用來進行哈希操做的整型代號,請不要把這個代號和前面所說的參閱變量所表明的代號弄混了。後者不只僅是個代號還具備在內存中才查找對 象的位置的功能。hashCode()所返回的值是用來分類對象在一些特定的收集對象中的位置。這些對象是HashMap, Hashtable, HashSet,等等。這個函數和上面的equals()函數必須本身設計,用來協助HashMap, Hashtable, HashSet,等等對本身所收集的大量對象進行搜尋和定位。

這些收集對象究竟如何工做的,想象每一個元對象hashCode是一個箱子的 編碼,按照編碼,每一個元對象就是根據hashCode()提供的代號納入相應的箱子裏。全部的箱子加起來就是一個HashSet,HashMap,或 Hashtable對象,咱們須要尋找一個元對象時,先看它的代碼,就是hashCode()返回的整型值,這樣咱們找到它所在的箱子,而後在箱子裏,每 個元對象都拿出來一個個和咱們要找的對象進行對比,若是兩個對象的內容相等,咱們的搜尋也就結束。這種操做須要兩個重要的信息,一是對象的 hashCode(),還有一個是對象內容對比的結果。

hashCode()的返回值和equals()的關係以下:

若是x.equals(y)返回「true」,那麼x和y的hashCode()必須相等。 
若是x.equals(y)返回「false」,那麼x和y的hashCode()有可能相等,也有可能不等。 

爲何這兩個規則是這樣的,緣由其實很簡單,拿HashSet來講吧,HashSet能夠擁有一個或更多的箱子,在同一個箱子中能夠有一個 或更多的獨特元對象(HashSet所容納的必須是獨特的元對象)。這個例子說明一個元對象能夠和其餘不一樣的元對象擁有相同的hashCode。可是一個 元對象只能和擁有一樣內容的元對象相等。因此這兩個規則必須成立。

設計這兩個函數所要注意到的:
若是你設計的對象類型並不使用於收集性對象,那麼沒有必要本身再設計這兩個函數的處理方式。這是正確的面向對象設計方法,任何用戶一時用不到的功能,就先不要設計,以避免給往後功能擴展帶來麻煩。

若是你在設計時想別出心裁,不遵照以上的兩套規則,那麼勸你仍是不要作這樣想入非非的事。我尚未遇到過哪個開發者和我說設計這兩個函數要違背前面說的兩個規則,我碰到這些違反規則的狀況時,都是做爲設計錯誤處理。

當一個對象類型做爲收集型對象的元對象時,這個對象應該擁有本身處理equals(),和/或處理hashCode()的設計,並且要遵照前面所說 的兩種原則。equals()先要查null和是不是同一類型。查同一類型是爲了不出現ClassCastException這樣的異常給丟出來。查 null是爲了不出現NullPointerException這樣的異常給丟出來。

若是你的對象裏面容納的數據過多,那麼這兩個函數 equals()和hashCode()將會變得效率低。若是對象中擁有沒法serialized的數據,equals()有可能在操做中出現錯誤。想象 一個對象x,它的一個整型數據是transient型(不能被serialize成二進制數據流)。然而equals()和hashCode()都有依靠 這個整型數據,那麼,這個對象在serialization以前和以後,是否同樣?答案是不同。由於serialization以前的整型數據是有效的 數據,在serialization以後,這個整型數據的值並無存儲下來,再從新由二進制數據流轉換成對象後,二者(對象在serialization 以前和以後)的狀態已經不一樣了。這也是要注意的。 


============================================================      
有效和正肯定義hashCode()和equals():
============================================================      

  級別:入門級


  每一個Java對象都有hashCode()和 equals()方法。許多類忽略(Override)這些方法的缺省實施,以在對象實例之間提供更深層次的語義可比性。在Java理念和實踐這一部分, Java開發人員Brian Goetz向您介紹在建立Java類以有效和準肯定義hashCode()和equals()時應遵循的規則和指南。您能夠在討論論壇與做者和其它讀者一同探討您對本文的見解。(您還能夠點擊本文頂部或底部的討論進入論壇。)
  
  雖然Java語言不直接支持關聯數組 -- 可使用任何對象做爲一個索引的數組 -- 但在根Object類中使用hashCode()方法明確表示指望普遍使用HashMap(及其前輩Hashtable)。理想狀況下基於散列的容器提供有效插入和有效檢索;直接在對象模式中支持散列能夠促進基於散列的容器的開發和使用。

  定義對象的相等性

  Object類有兩種方法來推斷對象的標識:equals()和hashCode()。通常來講,若是您忽略了其中一種,您必須同時忽略這兩種,由於二者之間有必須維持的相當重要的關係。特殊狀況是根據equals() 方法,若是兩個對象是相等的,它們必須有相同的hashCode()值(儘管這一般不是真的)。

  特定類的equals()的語義在Implementer的左側定義;定義對特定類來講equals()意味着什麼是其設計工做的一部分。Object提供的缺省實施簡單引用下面等式:

  public boolean equals(Object obj) { return (this == obj); }

  在這種缺省實施狀況下,只有它們引用真正同一個對象時這兩個引用纔是相等的。一樣,Object提供的 hashCode()的缺省實施經過將對象的內存地址對映於一個整數值來生成。因爲在某些架構上,地址空間大於int值的範圍,兩個不一樣的對象有相同的 hashCode()是可能的。若是您忽略了hashCode(),您仍舊可使用System.identityHashCode()方法來接入這類缺省值。

    忽略 equals() -- 簡單實例

  缺省狀況下,equals()和hashCode()基於標識的實施是合理的,但對於某些類來講,它們但願放寬等式的定義。例如,Integer類定義equals() 與下面相似:

  public boolean equals(Object obj) {
  return (obj instanceof Integer
  && intValue() == ((Integer) obj).intValue());
  }

  在這個定義中,只有在包含相同的整數值的狀況下這兩個Integer對象是相等的。結合將不可修改的 Integer,這使得使用Integer做爲HashMap中的關鍵字是切實可行的。這種基於值的Equal方法能夠由Java類庫中的全部原始封裝類使用,如Integer、Float、Character和Boolean以及String(若是兩個String對象包含相同順序的字符,那它們是相等的)。因爲這些類都是不可修改的而且能夠實施hashCode()和equals(),它們均可以作爲很好的散列關鍵字。

  爲何忽略 equals()和hashCode()?

  若是Integer不忽略equals() 和 hashCode()狀況又將如何?若是咱們從未在HashMap或其它基於散列的集合中使用Integer做爲關鍵字的話,什麼也不會發生。可是,若是咱們在HashMap中使用這類Integer對象做爲關鍵字,咱們將不可以可靠地檢索相關的值,除非咱們在get()調用中使用與put()調用中極其相似的Integer實例。這要求確保在咱們的整個程序中,只能使用對應於特定整數值的Integer對象的一個實例。不用說,這種方法極不方便並且錯誤頻頻。

  Object的interface contract要求若是根據 equals()兩個對象是相等的,那麼它們必須有相同的hashCode()值。當其識別能力整個包含在equals()中時,爲何咱們的根對象類須要hashCode()?hashCode()方法純粹用於提升效率。Java平臺設計人員預計到了典型Java應用程序中基於散列的集合類(Collection Class)的重要性--如Hashtable、HashMap和HashSet,而且使用equals()與許多對象進行比較在計算方面很是昂貴。使全部Java對象都可以支持 hashCode()並結合使用基於散列的集合,能夠實現有效的存儲和檢索。算法

==============================
Go deep into HashCode:
==============================

爲何HashCode對於對象是如此的重要?
一個對象的HashCode就是一個簡單的Hash算法的實現,雖然它和那些真正的複雜的Hash算法相比還不能叫真正的算法,但如何實現它,不只僅是程序員的編程水平問題,而是關係到你的對象在存取時性能的很是重要的問題.有可能,不一樣的HashCode可能會使你的對象存取產生,成百上千倍的性能差異.

咱們先來看一下,在JAVA中兩個重要的數據結構:HashMap和Hashtable,雖然它們有很大的區別,如繼承關係不一樣,對value的約束條件(是否容許null)不一樣,以及線程安全性等有着特定的區別,但從實現原理上來講,它們是一致的.因此,咱們只以Hashtable來講明:

在java中,存取數據的性能,通常來講固然是首推數組,可是在數據量稍大的容器選擇中,Hashtable將有比數據性能更高的查詢速度.具體緣由看下面的內容.

Hashtable在存儲數據時,通常先將該對象的HashCode和0x7FFFFFFF作與操做,由於一個對象的HashCode能夠爲負數,這樣操做後能夠保證它爲一個正整數.而後以Hashtable的長度取模,獲得該對象在Hashtable中的索引.

index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
這個對象就會直接放在Hashtable的第index位置,對於寫入,這和數組同樣,把一個對象放在其中的第index位置,但若是是查詢,通過一樣的算法,Hashtable能夠直接從第index取得這個對象,而數組卻要作循環比較.因此對於數據量稍大時,Hashtable的查詢比數據具備更高的性能.

既然能夠根據HashCode直接定位對象在Hashtable中的位置,那麼爲何Hashtable要用key來作映射呢(爲了一些思惟有障礙的人能看到懂我加了一句話:而不是直接放value呢)?這就是關係Hashtable性能問題的最重要的問題:Hash衝突.

常見的Hash衝突是不一樣對象最終產生了相同的索引,而一種很是甚至絕對少見的Hash衝突是,若是一組對象的個數大過了int範圍,而HashCode的長度只能在int範圍中,因此確定要有同一組的元素有相同的HashCode,這樣不管如何他們都會有相同的索引.固然這種極端的狀況是極少見的,能夠暫不考慮,但對於相同的HashCode通過取模,則會產中相同的索引,或者不一樣的對象卻具備相同的HashCode,固然具備相同的索引.

因此對於索引相同的對象,在該index位置存放了多個對象,這些值要想能正確區分,就要依靠key自己和hashCode來識別.

事實上一個設計各好的HashTable,通常來講會比較平均地分佈每一個元素,由於Hashtable的長度老是比實際元素的個數按必定比例進行自增(裝填因子通常爲0.75)左右,這樣大多數的索引位置只有一個對象,而不多的位置會有幾個對象.因此Hashtable中的每一個位置存放的是一個鏈表,對於只有一個對象的位置,鏈表只有一個首節點(Entry),Entry的next爲null.而後有hashCode,key,value. 屬性保存了該位置的對象的HashCode,key和value(對象自己),若是有相同索引的對象進來則會進入鏈表的下一個節點.若是同一個位置中有多個對象,根據HashCode和key能夠在該鏈表中找到一個和查詢的key相匹配的對象.

從上面我看能夠看到,對於HashMap和Hashtable的存取性能有重大影響的首先是應該使該數據結構中的元素儘可能大可能具備不一樣的HashCode,雖然這並不能保證不一樣的HashCode產生不一樣的index,但相同的HashCode必定產生相同的index,從而影響產生Hash衝突.

對於一個象,若是具備不少屬性,把全部屬性都參與散列,顯然是一種笨拙的設計.由於對象的HashCode()方法幾乎無所不在地被自動調用,如equals比較,若是太多的對象參與了散列.那麼須要的操做常數時間將會增長很大.因此,挑選哪些屬性參與散列絕對是一個編程水平的問題.

從實現來講,通常的HashCode方法會這樣:

return Attribute1.HashCode() + Attribute2.HashCode()...[+super.HashCode()],

咱們知道,每次調用這個方法,都要從新對方法內的參與散列的對象從新計算一次它們的HashCode的運算,若是一個對象的屬性沒有改變,仍然要每次都進行計算,因此若是設置一個標記來緩存當前的散列碼,只要當參與散列的對象改變時才從新計算,不然調用緩存的hashCode,這能夠從很大程度上提升性能.


默認的實現是將對象內部地址轉化爲整數做爲HashCode,這固然能保證每一個對象具備不一樣的HasCode,由於不一樣的對象內部地址確定不一樣(廢話),但java語言並不能讓程序員獲取對象內部地址,因此,讓每一個對象產生不一樣的HashCode有着不少可研究的技術.

如何從多個屬性中採樣出能具備多樣性的hashCode的屬性,這是一個性能和多樣性相矛盾的地方,若是全部屬性都參與散列,固然hashCode的多樣性將大大提升,但犧牲了性能,而若是隻有少許的屬性採樣散列,極端狀況會產生大量的散列衝突,如對"人"的屬性中,若是用性別而不是姓名或出生日期,那將只有兩個或幾個可選的hashcode值,將產生一半以上的散列衝突.因此若是可能的條件下,專門產生一個序列用來生成HashCode將是一個好的選擇(固然產生序列的性能要比全部屬性參與散列的性能高的狀況下才行,不然還不如直接用全部屬性散列).

如何對HashCode的性能和多樣性求得一個平衡,能夠參考相關算法設計的書,其實並不必定要求很是的優秀,只要能盡最大可能減小散列值的彙集.重要的是咱們應該記得HashCode對於咱們的程序性能有着生要的影響,在程序設計時應該時時加以注意.編程

相關文章
相關標籤/搜索