1. 程式人生 > >Java中hashcode的理解

Java中hashcode的理解

instance 論壇 區分 instant hset col 又一 guid ray

Java中hashcode的理解

原文鏈接http://blog.csdn.net/chinayuan/article/details/3345559

怎樣理解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.lang.Object中對hashCode的約定:

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

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

所以hashcode相等僅僅能保證兩個對象在一個HASH表裏的同一條HASH鏈上,繼而通過equals方法才幹確定是不是同一對象,假設結果為true, 則覺得是同一對象在插入。否則覺得是不同對象繼續插入。

Object的代碼:
public String toString () {
return this.getClass().getName() + “@” + Integer.toHexString(this.hashCode());
}

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

public native int hashCode();

從上面我們能夠看到是否非常可能Object.hashCode就是代表內存地址。以下我們來證明hashcode是不是真的就是Object的內存地址呢?實際上,hashcode根本不能代表object的內存地址。

Object.hashCode不能夠代表內存地址

package com.tools;

import java.util.ArrayList;

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表數據結構常識:
一、哈希表基於數組。
二、缺點:基於數組的。數組創建後難以擴展。

某些哈希表被基本填滿時。性能下降得非常嚴重。
三、沒有一種簡便得方法能夠以不論什麽一種順序遍歷表中數據項。
四、假設不須要有序遍歷數據,並且能夠提前預測數據量的大小。那麽哈希表在速度和易用性方面是無與倫比的。

一、為什麽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
&& intValue() == ((Integer) obj).intValue());
}
在 這個定義中,僅僅有在包括同樣的整數值的情況下這兩個Integer對象是相等的。

結合將不可改動的Integer,這使得使用Integer作為 HashMap中的keyword是切實可行的。這樣的基於值的Equal方法能夠由Java類庫中的全部原始封裝類使用。如Integer、Float、 Character和Boolean以及String(假設兩個String對象包括同樣順序的字符,那它們是相等的)。由於這些類都是不可改動的並且可 以實施hashCode()和equals()。它們都能夠做為非常好的散列keyword。
為什麽忽略 equals()和hashCode()?
如 果Integer不忽略equals() 和 hashCode()情況又將怎樣?假設我們從未在HashMap或其他基於散列的集合中使用Integer作為keyword的話。什麽也不會發生。可是,假設 我們在HashMap中使用這類Integer對象作為keyword,我們將不能夠可靠地檢索相關的值,除非我們在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() 必須一致 – 它們的結果在隨後的調用中將是同樣的。假設“不改變對象相等性比較中使用的不論什麽信息。”這聽起來象“計算的結果將不改變,除非實際情況如此。”這一模糊聲 明通常解釋為相等性和散列值計算應是對象的可確定性功能,而不是其他。
對象相等性意味著什麽?
人們非常easy滿足Object類規範對 equals() 和 hashCode() 的要求。決定是否和怎樣忽略equals()除了判斷以外。還要求其他。

在簡單的不可修值類中,如Integer(事實上是差點兒全部不可改動的類),選擇 相當明顯 – 相等性應基於基本對象狀態的相等性。在Integer情況下,對象的唯一狀態是主要的整數值。
對於可改動對象來說,答案並不總 是如此清楚。

equals() 和hashCode() 是否應基於對象的標識(象缺省實施)或對象的狀態(象Integer和String)?沒有簡單的答案 – 它取決於類的計劃使用。

對於象List和Map這樣的容器來說。人們對此爭論不已。Java類庫中的大多數類,包括容器類,錯誤出如今依據對象狀態來提供 equals()和hashCode()實施。
假設對象的hashCode()值能夠基於其狀態進行更改,那麽當使用這類對象作為基於散列的集 合中的keyword時我們必須註意。確保當它們用於作為散列keyword時,我們並不同意更改它們的狀態。全部基於散列的集合假設,當對象的散列值用於作為集合中的關 鍵字時它不會改變。

假設當keyword在集合中時它的散列代碼被更改,那麽將產生一些不可預測和easy混淆的結果。

實踐過程中這通常不是問題 – 我們並不常常使用象List這樣的可改動對象做為HashMap中的keyword。
一個簡單的可改動類的樣例是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))
&& ((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使用的訣竅都非常easy,但算術上還遠遠不夠完美。類庫不提供不論什麽散列算法的方便實施,它能夠簡化更先進的hashCode()實施的創建。
當擴 展已經忽略了equals()的 instantiable類時非常難編寫equals()。

當擴展已經忽略了equals()的 instantiable類時,定義equals()的“顯而易見的”方式都不能滿足equals()方法的對稱或傳遞性需求。這意味著當忽略 equals()時。您必須了解您正在擴展的類的結構和實施詳細信息,甚至須要暴露基本類中的機密字段,它違反了面向對象的設計的原則。
結束語
通 過統一定義equals()和hashCode()。您能夠提升類作為基於散列的集合中的keyword的使用性。有兩種方法來定義對象的相等性和散列值:基於標 識,它是Object提供的缺省方法;基於狀態,它要求忽略equals()和hashCode()。

當對象的狀態更改時假設對象的散列值發生變化,確信 當狀態作為散列keyword使用時您不同意更更改其狀態。

解析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()有可能相等,也有可能不等。

為 什麽這兩個規則是這樣的。原因事實上非常easy,拿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中的keyword是切實可行的。這樣的基於值的Equal方法能夠由Java類庫中的全部原始封裝類 使用。如Integer、Float、Character和Boolean以及String(假設兩個String對象包括同樣順序的字符。那它們是相等 的)。

由於這些類都是不可改動的並且能夠實施hashCode()和equals()。它們都能夠做為非常好的散列keyword。

  為什麽忽略 equals()和hashCode()?

   假設Integer不忽略equals() 和 hashCode()情況又將怎樣?假設我們從未在HashMap或其他基於散列的集合中使用Integer作為keyword的話。什麽也不會發生。可是,假設 我們在HashMap中使用這類Integer對象作為keyword,我們將不能夠可靠地檢索相關的值。除非我們在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對
於我們的程序性能有著生要的影響,在程序設計時應該時時加以註意.

Java中hashcode的理解