1. 程式人生 > >轉載equals()和hashCode()區別?

轉載equals()和hashCode()區別?

equals()和hashCode()區別?

-------------------------------------------------

equals():反映的是物件或變數具體的值,即兩個物件裡面包含的值--可能是物件的引用,也可能是值型別的值。

hashCode():計算出物件例項的雜湊碼,並返回雜湊碼,又稱為雜湊函式。根類Object的hashCode()方法的計算依賴於物件例項的D(記憶體地址),故每個Object物件的hashCode都是唯一的;當然,當物件所對應的類重寫了hashCode()方法時,結果就截然不同了。

  之所以有hashCode方法,是因為在批量的物件比較中,hashCode要比equals來得快,很多集合都用到了hashCode,比如HashTable。     兩個obj,如果equals()相等,hashCode()一定相等。   兩個obj,如果hashCode()相等,equals()不一定相等(Hash雜湊值有衝突的情況,雖然概率很低)。 所以:   可以考慮在集合中,判斷兩個物件是否相等的規則是:     第一步,如果hashCode()相等,則檢視第二步,否則不相等;     第二步,檢視equals()是否相等,如果相等,則兩obj相等,否則還是不相等。

1、首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。   equals()是對兩個物件的地址值進行的比較(即比較引用是否相同)。   hashCode()是一個本地方法,它的實現是根據本地機器相關的。 2、Java語言對equals()的要求如下,這些要求是必須遵循的:  A 對稱性:如果x.equals(y)返回是“true”,那麼y.equals(x)也應該返回是“true”。  B 反射性:x.equals(x)必須返回是“true”。  C 類推性:如果x.equals(y)返回是“true”,而且y.equals(z)返回是“true”,那麼z.equals(x)也應該返回是“true”。  D

 一致性:如果x.equals(y)返回是“true”,只要x和y內容一直不變,不管你重複x.equals(y)多少次,返回都是“true”。   任何情況下,x.equals(null),永遠返回是“false”;x.equals(和x不同型別的物件)永遠返回是“false”。 3、equals()相等的兩個物件,hashcode()一定相等;   反過來:hashcode()不等,一定能推出equals()也不等;   hashcode()相等,equals()可能相等,也可能不等。 

為什麼選擇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.

  可能經過上面理論的講一下大家都迷糊了,我也看了之後也是似懂非懂的。下面我舉個例子詳細說明下。

  list是可以重複的,set是不可以重複的。那麼set儲存資料的時候是怎樣判斷存進的資料是否已經存在。使用equals()方法呢,還是hashcode()方法。

  假如用equals(),那麼儲存一個元素就要跟已存在的所有元素比較一遍,比如已存入100個元素,那麼存101個元素的時候,就要呼叫equals方法100次。

  但如果用hashcode()方法的話,他就利用了hash演算法來儲存資料的。

  這樣的話每存一個數據就呼叫一次hashcode()方法,得到一個hashcode值及存入的位置。如果該位置不存在資料那麼就直接存入,否則呼叫一次equals()方法,不相同則存,相同不存。這樣下來整個儲存下來不需要呼叫幾次equals方法,雖然多了幾次hashcode方法,但相對於前面來講效率高了不少。

為什麼要重寫equals方法?

-------------------------------------------

  因為Object的equal方法預設是兩個物件的引用的比較,意思就是指向同一記憶體,地址則相等,否則不相等;如果你現在需要利用物件裡面的值來判斷是否相等,則過載equal方法。

  說道這個地方我相信很多人會有疑問,相信大家都被String物件的equals()方法和"=="糾結過一段時間,當時我們知道String物件中equals方法是判斷值的,而==是地址判斷。

那照這麼說equals怎麼會是地址的比較呢?

  那是因為實際上JDK中,String、Math等封裝類都對Object中的equals()方法進行了重寫。

  我們先看看Object中equals方法的原始碼:

1

2

3

public boolean equals(Object obj) { 

return (this == obj); 

}

我們都知道所有的物件都擁有標識(記憶體地址)和狀態(資料),同時“==”比較兩個物件的的記憶體地址,所以說使用Object的equals()方法是比較兩個物件的記憶體地址是否相等,即若object1.equals(object2)為true,則表示equals1和equals2實際上是引用同一個物件。雖然有時候Object的equals()方法可以滿足我們一些基本的要求,但是我們必須要清楚我們很大部分時間都是進行兩個物件的比較,這個時候Object的equals()方法就不可以了,所以才會有String這些類對equals方法的改寫,依次類推Double、Integer、Math。。。。等等這些類都是重寫了equals()方法的,從而進行的是內容的比較。希望大家不要搞混了。

改寫equals時總是要改寫hashcode

-------------------------------------------

java.lnag.Object中對hashCode的約定:    1. 在一個應用程式執行期間,如果一個物件的equals方法做比較所用到的資訊沒有被修改的話,則對該物件呼叫hashCode方法多次,它必須始終如一地返回同一個整數。    2. 如果兩個物件根據equals(Object o)方法是相等的,則呼叫這兩個物件中任一物件的hashCode方法必須產生相同的整數結果。    3. 如果兩個物件根據equals(Object o)方法是不相等的,則呼叫這兩個物件中任一個物件的hashCode方法,不要求產生不同的整數結果。但如果能不同,則可能提高散列表的效能。

根據上一個問題,實際上我們已經能很簡單的解釋這一點了,比如改寫String中的equals為基於內容上的比較而不是記憶體地址的話,那麼雖然equals相等,但並不代表記憶體地址相等,由hashcode方法的定義可知記憶體地址不同,沒改寫的hashcode值也可能不同。所以違背了第二條約定。

又如new一個物件,再new一個內容相等的物件,呼叫equals方法返回的true,但他們的hashcode值不同,將兩個物件存入HashSet中,會使得其中包含兩個相等的物件,因為是先檢索hashcode值,不等的情況下才會去比較equals方法的。

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的效能。即“分散原則”。   掌握了這兩條原則,你就能夠用好HashMap編寫自己的程式了。不知道大家注意沒有, java.lang.Object中提供的三個方法:clone(),equals()和hashCode()雖然很典型,但在很多情況下都不能夠適用,它們只是簡單的由物件的地址得出結果。這就需要我們在自己的程式中重寫它們,其實java類庫中也重寫了千千萬萬個這樣的方法。利用面向物件的多型性——覆蓋,Java的設計者很優雅的構建了Java的結構,也更加體現了Java是一門純OOP語言的特性。    Java提供的Collection和Map的功能是十分強大的,它們能夠使你的程式實現方式更為靈活,執行效率更高。希望本文能夠對大家更好的使用HashMap有所幫助。