#equals():反映的是對象或變量具體的值,即兩個對象里面包含的值--可能是對象的引用潭兽,也可能是值類型的值斗遏。
#hashCode():計算出對象實例的哈希碼,并返回哈希碼怒坯,又稱為散列函數(shù)。根類Object的hashCode()方法的計算依賴于對象實例的D(內(nèi)存地址)视译,故每個Object對象的hashCode都是唯一的归敬;當然汪茧,當對象所對應的類重寫了hashCode()方法時,結果就截然不同了呀舔。之所以有hashCode方法扩灯,是因為在批量的對象比較中,hashCode要比equals來得快惧磺,很多集合都用到了hashCode捻撑,比如HashTable。
#前提在沒有自定義equals()和hashCode()的情況下
兩個obj番捂,如果equals()相等江解,hashCode()一定相等膘流。
兩個obj鲁沥,如果hashCode()相等,equals()不一定相等(Hash散列值有沖突的情況彭谁,雖然概率很低)允扇。
所以:可以考慮在集合中则奥,判斷兩個對象是否相等的規(guī)則是:
第一步读处,如果hashCode()相等唱矛,則查看第二步,否則不相等;
第二步管闷,查看equals()是否相等窃肠,如果相等冤留,則兩obj相等,否則還是不相等呕臂。
1肪跋、首先equals()和hashcode()這兩個方法都是從object類中繼承過來的。
equals()是對兩個對象的地址值進行的比較(即比較引用是否相同)谜洽。
hashCode()是一個本地方法吴叶,它的實現(xiàn)是根據(jù)本地機器相關的蚌卤。
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內(nèi)容一直不變,不管你重復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的比較或者取這個對象的時候,它會根據(jù)對象的hashcode再從Hash表中取這個對象针姿。這樣做的目的是提高取對象的效率厌衙。
#具體過程是這樣:
1. new Object(),JVM根據(jù)這個對象的Hashcode值,放入到對應的Hash表對應的Key上,如果不同的對象確產(chǎn)生了相同的hash值,也就是發(fā)生了Hash key相同導致沖突的情況,那么就在這個Hash key的地方產(chǎn)生一個鏈表,將所有產(chǎn)生相同hashcode的對象放到這個單鏈表上去,串在一起婶希。
2. 比較兩個對象的時候,首先根據(jù)他們的hashcode去hash表中找他的對象,當兩個對象的hashcode相同,那么就是說他們這兩個對象放在Hash表中的同一個key上,那么他們一定在這個key上的鏈表上。那么此時就只能根據(jù)Object的equal方法來比較這個對象是否equal彤枢。當兩個對象的hashcode不同的話筒饰,肯定他們不能equal.
可能經(jīng)過上面理論的講一下大家都迷糊了瓷们,我也看了之后也是似懂非懂的。下面我舉個例子詳細說明下式镐。
list是可以重復的固蚤,set是不可以重復的夕玩。那么set存儲數(shù)據(jù)的時候是怎樣判斷存進的數(shù)據(jù)是否已經(jīng)存在。使用equals()方法呢禽作,還是hashcode()方法揩页。假如用equals(),那么存儲一個元素就要跟已存在的所有元素比較一遍萍程,比如已存入100個元素茫负,那么存101個元素的時候,就要調(diào)用equals方法100次潮尝。但如果用hashcode()方法的話饿序,他就利用了hash算法來存儲數(shù)據(jù)的原探。
這樣的話每存一個數(shù)據(jù)就調(diào)用一次hashcode()方法,得到一個hashcode值及存入的位置告匠。如果該位置不存在數(shù)據(jù)那么就直接存入离唬,否則調(diào)用一次equals()方法输莺,不相同則存,相同不存型凳。這樣下來整個存儲下來不需要調(diào)用幾次equals方法嘱函,雖然多了幾次hashcode方法往弓,但相對于前面來講效率高了不少。
為什么要重寫equals方法槐脏?
因為Object的equal方法默認是兩個對象的引用的比較撇寞,意思就是指向同一內(nèi)存,地址則相等,否則不相等祖灰;如果你現(xiàn)在需要利用對象里面的值來判斷是否相等畔规,則重載equal方法叁扫。
說道這個地方我相信很多人會有疑問畜埋,相信大家都被String對象的equals()方法和"=="糾結過一段時間,當時我們知道String對象中equals方法是判斷值的对室,而==是地址判斷掩宜。
那照這么說equals怎么會是地址的比較呢么翰?
那是因為實際上JDK中浩嫌,String、Math等封裝類都對Object中的equals()方法進行了重寫追迟。
我們先看看Object中equals方法的源碼:
public?boolean?equals(Object obj) {?
? ? ? ? ?return?(this?== obj);
}
我們都知道所有的對象都擁有標識(內(nèi)存地址)和狀態(tài)(數(shù)據(jù))骚腥,同時“==”比較兩個對象的的內(nèi)存地址束铭,所以說使用Object的equals()方法是比較兩個對象的內(nèi)存地址是否相等,即若object1.equals(object2)為true剿骨,則表示equals1和equals2實際上是引用同一個對象浓利。雖然有時候Object的equals()方法可以滿足我們一些基本的要求,但是我們必須要清楚我們很大部分時間都是進行兩個對象的比較嫡秕,這個時候Object的equals()方法就不可以了苹威,所以才會有String這些類對equals方法的改寫牙甫,依次類推Double、Integer泻轰、Math浮声。旋奢。至朗。。等等這些類都是重寫了equals()方法的筑煮,從而進行的是內(nèi)容的比較粤蝎。希望大家不要搞混了初澎。
改寫equals時總是要改寫hashcode
java.lnag.Object中對hashCode的約定:
1. 在一個應用程序執(zhí)行期間碑宴,如果一個對象的equals方法做比較所用到的信息沒有被修改的話,則對該對象調(diào)用hashCode方法多次祸挪,它必須始終如一地返回同一個整數(shù)贞间。
2. 如果兩個對象根據(jù)equals(Object o)方法是相等的,則調(diào)用這兩個對象中任一對象的hashCode方法必須產(chǎn)生相同的整數(shù)結果整以。
3. 如果兩個對象根據(jù)equals(Object o)方法是不相等的公黑,則調(diào)用這兩個對象中任一個對象的hashCode方法凡蚜,不要求產(chǎn)生不同的整數(shù)結果。但如果能不同,則可能提高散列表的性能芹务。
根據(jù)上一個問題鸭廷,實際上我們已經(jīng)能很簡單的解釋這一點了辆床,比如改寫String中的equals為基于內(nèi)容上的比較而不是內(nèi)存地址的話,那么雖然equals相等讼载,但并不代表內(nèi)存地址相等轿秧,由hashcode方法的定義可知內(nèi)存地址不同,沒改寫的hashcode值也可能不同咨堤。所以違背了第二條約定菇篡。
又如new一個對象,再new一個內(nèi)容相等的對象一喘,調(diào)用equals方法返回的true驱还,但他們的hashcode值不同,將兩個對象存入HashSet中凸克,會使得其中包含兩個相等的對象议蟆,因為是先檢索hashcode值萎战,不等的情況下才會去比較equals方法的咐容。
hashCode方法使用介紹
Hash表數(shù)據(jù)結構常識:
一、哈希表基于數(shù)組蚂维。
二疟丙、缺點:基于數(shù)組的颖侄,數(shù)組創(chuàng)建后難以擴展。某些哈希表被基本填滿時享郊,性能下降得非常嚴重览祖。
三、沒有一種簡便得方法可以以任何一種順序遍歷表中數(shù)據(jù)項炊琉。
四展蒂、如果不需要有序遍歷數(shù)據(jù),并且可以提前預測數(shù)據(jù)量的大小苔咪,那么哈希表在速度和易用性方面是無與倫比的锰悼。
#
一、為什么HashCode對于對象是如此的重要:
一個對象的HashCode就是一個簡單的Hash算法的實現(xiàn)团赏,雖然它和那些真正的復雜的Hash算法相比還不能叫真正的算法箕般,它如何實現(xiàn)它,不僅僅是程序員的編程水平問題舔清,而是關系到你的對象在存取是性能的非常重要的關系.有可能丝里,不同的HashCode可能會使你的對象存取產(chǎn)生,成百上千倍的性能差別.
先來看一下体谒,在JAVA中兩個重要的數(shù)據(jù)結構:HashMap和Hashtable杯聚,雖然它們有很大的區(qū)別,如繼承關系不同抒痒,對value的約束條件(是否允許null)不同幌绍,以及線程安全性等有著特定的區(qū)別,但從實現(xiàn)原理上來說故响,它們是一致的.所以傀广,我們只以Hashtable來說明:
在java中,存取數(shù)據(jù)的性能彩届,一般來說當然是首推數(shù)組主儡,但是在數(shù)據(jù)量稍大的容器選擇中,Hashtable將有比數(shù)組性能更高的查詢速度.具體原因看下面的內(nèi)容.
Hashtable在存儲數(shù)據(jù)時惨缆,一般先將該對象的HashCode和0x7FFFFFFF做與操作糜值,因為一個對象的HashCode可以為負數(shù),這樣操作后可以保證它為一個正整數(shù).然后以Hashtable的長度取模坯墨,得到該對象在Hashtable中的索引.
index = (o.hashCode() & 0x7FFFFFFF)%hs.length;
這個對象就會直接放在Hashtable的每index位置寂汇,對于寫入,這和數(shù)組一樣捣染,把一個對象放在其中的第index位置骄瓣,但如果是查詢,經(jīng)過同樣的算法耍攘,Hashtable可以直接從第index取得這個對象榕栏,而數(shù)組卻要做循環(huán)比較.所以對于數(shù)據(jù)量稍大時畔勤,Hashtable的查詢比數(shù)組具有更高的性能.
既然一個對象可以根據(jù)HashCode直接定位它在Hashtable中的位置,那么為什么Hashtable還要用key來做映射呢?這就是關系Hashtable性能問題的最重要的問題:Hash沖突.
常見的Hash沖突是不同對象最終產(chǎn)生了相同的索引扒磁,而一種非常甚至絕對少見的Hash沖突是庆揪,如果一組對象的個數(shù)大過了int范圍,而HashCode的長度只能在int范圍中妨托,所以肯定要有同一組的元素有相同的HashCode缸榛,這樣無論如何他們都會有相同的索引.當然這種極端的情況是極少見的,可以暫不考慮兰伤,但是對于同的HashCode經(jīng)過取模内颗,則會產(chǎn)中相同的索引,或者不同的對象卻具有相同的HashCode敦腔,當然具有相同的索引.
所以對于索引相同的對象均澳,在該index位置存放了多個值,這些值要想能正確區(qū)分符衔,就要依靠key來識別.
事實上一個設計各好的HashTable找前,一般來說會比較平均地分布每個元素,因為Hashtable的長度總是比實際元素的個數(shù)按一定比例進行自增(裝填因子一般為0.75)左右柏腻,這樣大多數(shù)的索引位置只有一個對象纸厉,而很少的位置會有幾個元素.所以Hashtable中的每個位置存放的是一個鏈表系吭,對于只有一個對象是位置五嫂,鏈表只有一個首節(jié)點(Entry),Entry的next為null.然后有hashCode肯尺,key沃缘,value屬性保存了該位置的對象的HashCode,key和value(對象本身)则吟,如果有相同索引的對象進來則會進入鏈表的下一個節(jié)點.如果同一個索引中有多個對象槐臀,根據(jù)HashCode和key可以在該鏈表中找到一個和查詢的key相匹配的對象.
從上面我看可以看到,對于HashMap和Hashtable的存取性能有重大影響的首先是應該使該數(shù)據(jù)結構中的元素盡量大可能具有不同的HashCode氓仲,雖然這并不能保證不同的HashCode產(chǎn)生不同的index水慨,但相同的HashCode一定產(chǎn)生相同的index,從而影響產(chǎn)生Hash沖突.
對于一個象敬扛,如果具有很多屬性晰洒,把所有屬性都參與散列,顯然是一種笨拙的設計.因為對象的HashCode()方法幾乎無所不在地被自動調(diào)用啥箭,如equals比較谍珊,如果太多的對象參與了散列.
那么需要的操作常數(shù)時間將會增加很大.所以,挑選哪些屬性參與散列絕對是一個編程水平的問題.
從實現(xiàn)來說急侥,一般的HashCode方法會這樣:
return Attribute1.HashCode() Attribute1.HashCode()..[ super.HashCode()]砌滞,我們知道侮邀,每次調(diào)用這個方法,都要重新對方法內(nèi)的參與散列的對象重新計算一次它們的HashCode的運算贝润,如果一個對象的屬性沒有改變绊茧,仍然要每次都進行計算,所以如果設置一個標記來緩存當前的散列碼题暖,只要當參與散列的對象改變時才重新計算按傅,否則調(diào)用緩存的hashCode,這可以從很大程度上提高性能.
默認的實現(xiàn)是將對象內(nèi)部地址轉化為整數(shù)作為HashCode胧卤,這當然能保證每個對象具有不同的HasCode唯绍,因為不同的對象內(nèi)部地址肯定不同(廢話),但java語言并不能讓程序員獲取對象內(nèi)部地址枝誊,所以况芒,讓每個對象產(chǎn)生不同的HashCode有著很多可研究的技術.
如果從多個屬性中采樣出能具有平均分布的hashCode的屬性,這是一個性能和多樣性相矛盾的地方叶撒,如果所有屬性都參與散列绝骚,當然hashCode的多樣性將大大提高,但犧牲了性能祠够,而如果只能少量的屬性采樣散列压汪,極端情況會產(chǎn)生大量的散列沖突,如對"人"的屬性中古瓤,如果用性別而不是姓名或出生日期止剖,那將只有兩個或幾個可選的hashcode值,將產(chǎn)生一半以上的散列沖突.所以如果可能的條件下落君,專門產(chǎn)生一個序列用來生成HashCode將是一個好的選擇(當然產(chǎn)生序列的性能要比所有屬性參與散列的性能高的情況下才行穿香,否則還不如直接用所有屬性散列).
如何對HashCode的性能和多樣性求得一個平衡,可以參考相關算法設計的書绎速,其實并不一定要求非常的優(yōu)秀皮获,只要能盡最大可能減少散列值的聚集.重要的是我們應該記得HashCode對于我們的程序性能有著重要的影響,在程序設計時應該時時加以注意.
請記孜圃:如果你想有效的使用HashMap洒宝,你就必須重寫在其的HashCode()。
還有兩條重寫HashCode()的原則:
不必對每個不同的對象都產(chǎn)生一個唯一的hashcode萌京,只要你的HashCode方法使get()能夠得到put()放進去的內(nèi)容就可以了雁歌。即“不為一原則”。生成hashcode的算法盡量使hashcode的值分散一些枫夺, 不要很多hashcode都集中在一個范圍內(nèi)将宪,這樣有利于提高HashMap的性能。即“分散原則”。
掌握了這兩條原則较坛,你就能夠用好HashMap編寫自己的程序了印蔗。不知道大家注意沒有, java.lang.Object中提供的三個方法:clone()丑勤,equals()和hashCode()雖然很典型华嘹,但在很多情況下都不能夠適用,它們只是簡單的由對象的地址得出結果法竞。這就需要我們在自己的程序中重寫它們耙厚,其實java類庫中也重寫了千千萬萬個這樣的方法。利用面向對象的多態(tài)性——覆蓋岔霸,Java的設計者很優(yōu)雅的構建了Java的結構薛躬,也更加體現(xiàn)了Java是一門純OOP語言的特性。
Java提供的Collection和Map的功能是十分強大的呆细,它們能夠使你的程序實現(xiàn)方式更為靈活型宝,執(zhí)行效率更高。希望本文能夠對大家更好的使用HashMap有所幫助絮爷。
#