Java中hashcode的理解 css
以 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上的鏈表上。java
那麼此時就僅僅能依據Object的equal方法來比較這個對象是否equal。當兩個對象的hashcode不一樣的話,確定 他們不能equal.算法
============================================================ 編程
java.lang.Object中對hashCode的約定:數組
有一個概念要牢記。兩個相等對象的equals方法必定爲true, 但兩個hashcode相等的對象不必定是相等的對象。緩存
因此hashcode相等僅僅能保證兩個對象在一個HASH表裏的同一條HASH鏈上,繼而經過equals方法才幹肯定是否是同一對象,假設結果爲true, 則以爲是同一對象在插入。不然以爲是不一樣對象繼續插入。安全
Object的代碼:
public String toString () {
return this.getClass().getName() + 「@」 + Integer.toHexString(this.hashCode());
}markdown
public boolean equals (Object o) {
return this == o;
}數據結構
public native int 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()); }
}
==============================
==============================
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
&& 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;
依據您使用的類。您可能但願減小 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 以前和以後)的狀態已經不一樣了。
這也是要注意的。
============================================================
級別:入門級
每個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()並結合使用基於散列的集合,可以實現有效的存儲和檢索。
==============================
爲何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對 於咱們的程序性能有着生要的影響,在程序設計時應該時時加以注意.