equals()和hashCode()區別?html
-------------------------------------------------java
equals():反映的是對象或變量具體的值,即兩個對象裏面包含的值--多是對象的引用,也多是值類型的值。
程序員
hashCode():計算出對象實例的哈希碼,並返回哈希碼,又稱爲散列函數。根類Object的hashCode()方法的計算依賴於對象實例的D(內存地址),故每一個Object對象的hashCode都是惟一的;固然,當對象所對應的類重寫了hashCode()方法時,結果就大相徑庭了。算法
之因此有hashCode方法,是由於在批量的對象比較中,hashCode要比equals來得快,不少集合都用到了hashCode,好比HashTable。
兩個obj,若是equals()相等,hashCode()必定相等。
兩個obj,若是hashCode()相等,equals()不必定相等(Hash散列值有衝突的狀況,雖然機率很低)。
因此:
能夠考慮在集合中,判斷兩個對象是否相等的規則是:
第一步,若是hashCode()相等,則查看第二步,不然不相等;
第二步,查看equals()是否相等,若是相等,則兩obj相等,不然仍是不相等。編程
一、首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。
equals()是對兩個對象的地址值進行的比較(即比較引用是否相同)。
hashCode()是一個本地方法,它的實現是根據本地機器相關的。
二、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」。
三、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表數據結構常識:
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的性能。即「分散原則」。
掌握了這兩條原則,你就可以用好HashMap編寫本身的程序了。不知道你們注意沒有, java.lang.Object中提供的三個方法:clone(),equals()和hashCode()雖然很典型,但在不少狀況下都不可以適用,它們只是簡單的由對象的地址得出結果。這就須要咱們在本身的程序中重寫它們,其實java類庫中也重寫了千千萬萬個這樣的方法。利用面向對象的多態性——覆蓋,Java的設計者很優雅的構建了Java的結構,也更加體現了Java是一門純OOP語言的特性。
Java提供的Collection和Map的功能是十分強大的,它們可以使你的程序實現方式更爲靈活,執行效率更高。但願本文可以對你們更好的使用HashMap有所幫助。