hashCode

hashcode() 方法詳解

hashCode()方法給對象返回一個(gè)hash code值。這個(gè)方法被用于hash tables,例如HashMap。

它的性質(zhì)是:

  • 在一個(gè)Java應(yīng)用的執(zhí)行期間,如果一個(gè)對象提供給equals做比較的信息沒有被修改的話,該對象多次調(diào)用hashCode()方法,該方法必須始終如一返回同一個(gè)integer。

  • 如果兩個(gè)對象根據(jù)equals(Object)方法是相等的,那么調(diào)用二者各自的hashCode()方法必須產(chǎn)生同一個(gè)integer結(jié)果。

  • 并不要求根據(jù)equals(java.lang.Object)方法不相等的兩個(gè)對象,調(diào)用二者各自的hashCode()方法必須產(chǎn)生不同的integer結(jié)果。然而,程序員應(yīng)該意識到對于不同的對象產(chǎn)生不同的integer結(jié)果,有可能會提高h(yuǎn)ash table的性能。

大量的實(shí)踐表明,由Object類定義的hashCode()方法對于不同的對象返回不同的integer。

在object類中,hashCode定義如下:

public native int hashCode();

說明是一個(gè)本地方法,它的實(shí)現(xiàn)是根據(jù)本地機(jī)器相關(guān)的。當(dāng)然我們可以在自己寫的類中覆蓋hashcode()方法,比如String、Integer、Double等這些類都是覆蓋了hashcode()方法的。例如在String類中定義的hashcode()方法如下:

public boolean equals(Object anObject) {  
    if (this == anObject) {  
        return true;  
    }  
    if (anObject instanceof String) {  
        String anotherString = (String)anObject;  
        int n = count;  
        if (n == anotherString.count) {  
            char v1[] = value;  
            char v2[] = anotherString.value;  
            int i = offset;  
            int j = anotherString.offset;  
            while (n– != 0) {  
                if (v1[i++] != v2[j++])  
                    return false;  
            }  
            return true;  
        }  
    }  
    return false;  
}  

解釋一下這個(gè)程序(String的API中寫到):s[0]31^(n-1) + s[1]31^(n-2) + … + s[n-1]
使用 int 算法,這里 s[i] 是字符串的第 i 個(gè)字符,n 是字符串的長度,^ 表示求冪(空字符串的哈希碼為 0)。
想要弄明白hashCode的作用,必須要先知道Java中的集合。  
總的來說,Java中的集合(Collection)有兩類,一類是List,再有一類是Set。前者集合內(nèi)的元素是有序的,元素可以重復(fù);后者元素?zé)o序,但元素不可重復(fù)。這里就引出一個(gè)問題:要想保證元素不重復(fù),可兩個(gè)元素是否重復(fù)應(yīng)該依據(jù)什么來判斷呢?
這就是Object.equals方法了。但是,如果每增加一個(gè)元素就檢查一次,那么當(dāng)元素很多時(shí),后添加到集合中的元素比較的次數(shù)就非常多了。也就是說,如果集合中現(xiàn)在已經(jīng)有1000個(gè)元素,那么第1001個(gè)元素加入集合時(shí),它就要調(diào)用1000次equals方法。這顯然會大大降低效率。
于是,Java采用了哈希表的原理。哈希(Hash)實(shí)際上是個(gè)人名,由于他提出一哈希算法的概念,所以就以他的名字命名了。哈希算法也稱為散列算法,是將數(shù)據(jù)依特定算法直接指定到一個(gè)地址上,初學(xué)者可以簡單理解,hashCode方法實(shí)際上返回的就是對象存儲的物理地址(實(shí)際可能并不是)。
這樣一來,當(dāng)集合要添加新的元素時(shí),先調(diào)用這個(gè)元素的hashCode方法,就一下子能定位到它應(yīng)該放置的物理位置上。如果這個(gè)位置上沒有元素,它就可以直接存儲在這個(gè)位置上,不用再進(jìn)行任何比較了;如果這個(gè)位置上已經(jīng)有元素了,就調(diào)用它的equals方法與新元素進(jìn)行比較,相同的話就不存了,不相同就散列其它的地址。所以這里存在一個(gè)沖突解決的問題。這樣一來實(shí)際調(diào)用equals方法的次數(shù)就大大降低了,幾乎只需要一兩次。

** 簡而言之,在集合查找時(shí),hashcode能大大降低對象比較次數(shù),提高查找效率!**

Java對象的eqauls方法和hashCode方法是這樣規(guī)定的:

1、相等(相同)的對象必須具有相等的哈希碼(或者散列碼)。

2、如果兩個(gè)對象的hashCode相同,它們并不一定相同。

關(guān)于第一點(diǎn),相等(相同)的對象必須具有相等的哈希碼(或者散列碼),為什么?

想象一下,假如兩個(gè)Java對象A和B,A和B相等(eqauls結(jié)果為true),但A和B的哈希碼不同,則A和B存入HashMap時(shí)的哈希碼計(jì)算得到的HashMap內(nèi)部數(shù)組位置索引可能不同,那么A和B很有可能允許同時(shí)存入HashMap,顯然相等/相同的元素是不允許同時(shí)存入HashMap,HashMap不允許存放重復(fù)元素。

關(guān)于第二點(diǎn),兩個(gè)對象的hashCode相同,它們并不一定相同

也就是說,不同對象的hashCode可能相同;假如兩個(gè)Java對象A和B,A和B不相等(eqauls結(jié)果為false),但A和B的哈希碼相等,將A和B都存入HashMap時(shí)會發(fā)生哈希沖突,也就是A和B存放在HashMap內(nèi)部數(shù)組的位置索引相同這時(shí)HashMap會在該位置建立一個(gè)鏈接表,將A和B串起來放在該位置,顯然,該情況不違反HashMap的使用原則,是允許的。當(dāng)然,哈希沖突越少越好,盡量采用好的哈希算法以避免哈希沖突。

所以,Java對于eqauls方法和hashCode方法是這樣規(guī)定的:

1.如果兩個(gè)對象相同,那么它們的hashCode值一定要相同;
2.如果兩個(gè)對象的hashCode相同,它們并不一定相同(這里說的對象相同指的是用eqauls方法比較)。
如不按要求去做了,會發(fā)現(xiàn)相同的對象可以出現(xiàn)在Set集合中,同時(shí),增加新元素的效率會大大下降。
3.equals()相等的兩個(gè)對象,hashcode()一定相等;equals()不相等的兩個(gè)對象,卻并不能證明他們的hashcode()不相等。
換句話說,equals()方法不相等的兩個(gè)對象,hashcode()有可能相等(我的理解是由于哈希碼在生成的時(shí)候產(chǎn)生沖突造成的)。反過來,hashcode()不等,一定能推出equals()也不等;hashcode()相等,equals()可能相等,也可能不等。

    在object類中,hashcode()方法是本地方法,返回的是對象的地址值,而object類中的equals()方法比較的也是兩個(gè)對象的地址值,如果equals()相等,說明兩個(gè)對象地址值也相等,當(dāng)然hashcode()也就相等了;**在String類中,equals()返回的是兩個(gè)對象內(nèi)容的比較**,當(dāng)兩個(gè)對象內(nèi)容相等時(shí),Hashcode()方法根據(jù)String類的重寫代碼的分析,也可知道hashcode()返回結(jié)果也會相等。以此類推,可以知道Integer、Double等封裝類中經(jīng)過重寫的equals()和hashcode()方法也同樣適合于這個(gè)原則。當(dāng)然沒有經(jīng)過重寫的類,在繼承了object類的equals()和hashcode()方法后,也會遵守這個(gè)原則。

Hashset、Hashmap、Hashtable與hashcode()和equals()的密切關(guān)系

Hashset是繼承Set接口,Set接口又實(shí)現(xiàn)Collection接口,這是層次關(guān)系。那么Hashset、Hashmap、Hashtable中的存儲操作是根據(jù)什么原理來存取對象的呢?

下面以HashSet為例進(jìn)行分析,我們都知道:在hashset中不允許出現(xiàn)重復(fù)對象,元素的位置也是不確定的。在hashset中又是怎樣判定元素是否重復(fù)的呢?在java的集合中,判斷兩個(gè)對象是否相等的規(guī)則是:
1.判斷兩個(gè)對象的hashCode是否相等
如果不相等,認(rèn)為兩個(gè)對象也不相等,完畢
如果相等,轉(zhuǎn)入2
(這一點(diǎn)只是為了提高存儲效率而要求的,其實(shí)理論上沒有也可以,但如果沒有,實(shí)際使用時(shí)效率會大大降低,所以我們這里將其做為必需的。)
2.判斷兩個(gè)對象用equals運(yùn)算是否相等
如果不相等,認(rèn)為兩個(gè)對象也不相等
如果相等,認(rèn)為兩個(gè)對象相等(equals()是判斷兩個(gè)對象是否相等的關(guān)鍵)
為什么是兩條準(zhǔn)則,難道用第一條不行嗎?不行,因?yàn)榍懊嬉呀?jīng)說了,hashcode()相等時(shí),equals()方法也可能不等,所以必須用第2條準(zhǔn)則進(jìn)行限制,才能保證加入的為非重復(fù)元素。

例1:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 package com.bijian.study; 2
3 import java.util.HashSet; 4 import java.util.Iterator; 5 import java.util.Set; 6
7 public class HashSetTest { 8
9 public static void main(String args[]) { 10 String s1 = new String("aaa"); 11 String s2 = new String("aaa"); 12 System.out.println(s1 == s2); 13 System.out.println(s1.equals(s2)); 14 System.out.println(s1.hashCode()); 15 System.out.println(s2.hashCode()); 16 Set hashset = new HashSet(); 17 hashset.add(s1); 18 hashset.add(s2); 19 Iterator it = hashset.iterator(); 20 while (it.hasNext()) { 21 System.out.println(it.next()); 22 } 23 } 24 }</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

運(yùn)行結(jié)果:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">false
true
96321
96321 aaa</pre>

這是因?yàn)镾tring類已經(jīng)重寫了equals()方法和hashcode()方法,所以hashset認(rèn)為它們是相等的對象,進(jìn)行了重復(fù)添加。

例2:

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;"> 1 package com.bijian.study; 2
3 import java.util.HashSet; 4 import java.util.Iterator; 5
6 public class HashSetTest { 7
8 public static void main(String[] args) { 9 HashSet hs = new HashSet(); 10 hs.add(new Student(1, "zhangsan")); 11 hs.add(new Student(2, "lisi")); 12 hs.add(new Student(3, "wangwu")); 13 hs.add(new Student(1, "zhangsan")); 14
15 Iterator it = hs.iterator(); 16 while (it.hasNext()) { 17 System.out.println(it.next()); 18 } 19 } 20 } 21
22 class Student { 23 int num; 24 String name; 25
26 Student(int num, String name) { 27 this.num = num; 28 this.name = name; 29 } 30
31 public String toString() { 32 return num + ":" + name; 33 } 34 }</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

運(yùn)行結(jié)果:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1:zhangsan 3:wangwu 2:lisi 1:zhangsan </pre>

為什么hashset添加了相等的元素呢,這是不是和hashset的原則違背了呢?回答是:沒有。因?yàn)樵诟鶕?jù)hashcode()對兩次建立的new Student(1,“zhangsan”)對象進(jìn)行比較時(shí),生成的是不同的哈希碼值,所以hashset把他當(dāng)作不同的對象對待了,當(dāng)然此時(shí)的equals()方法返回的值也不等。

    為什么會生成不同的哈希碼值呢?上面我們在比較s1和s2的時(shí)候不是生成了同樣的哈希碼嗎?原因就在于我們自己寫的Student類并沒有重新自己的hashcode()和equals()方法,所以在比較時(shí),是繼承的object類中的hashcode()方法,而object類中的hashcode()方法是一個(gè)本地方法,比較的是對象的地址(引用地址),使用new方法創(chuàng)建對象,兩次生成的當(dāng)然是不同的對象了,造成的結(jié)果就是兩個(gè)對象的hashcode()返回的值不一樣,所以Hashset會把它們當(dāng)作不同的對象對待。

    怎么解決這個(gè)問題呢?答案是:在Student類中重新hashcode()和equals()方法。

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">class Student { int num;
String name;

Student(int num, String name) { this.num = num; this.name = name;
} public int hashCode() { return num * name.hashCode();
} public boolean equals(Object o) {
    Student s = (Student) o; return num == s.num && name.equals(s.name);
} public String toString() { return num + ":" + name;
}

}</pre>

[
復(fù)制代碼

](javascript:void(0); "復(fù)制代碼")

運(yùn)行結(jié)果:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">1:zhangsan 3:wangwu 2:lisi </pre>

可以看到重復(fù)元素的問題已經(jīng)消除,根據(jù)重寫的方法,即便兩次調(diào)用了new Student(1,"zhangsan"),我們在獲得對象的哈希碼時(shí),根據(jù)重寫的方法hashcode(),獲得的哈希碼肯定是一樣的,當(dāng)然根據(jù)equals()方法我們也可判斷是相同的,所以在向hashset集合中添加時(shí)把它們當(dāng)作重復(fù)元素看待了。

重寫equals()和hashcode()小結(jié):

1.重點(diǎn)是equals,重寫hashCode只是技術(shù)要求(為了提高效率)
2.為什么要重寫equals呢?因?yàn)樵趈ava的集合框架中,是通過equals來判斷兩個(gè)對象是否相等的
3.在hibernate中,經(jīng)常使用set集合來保存相關(guān)對象,而set集合是不允許重復(fù)的。在向HashSet集合中添加元素時(shí),其實(shí)只要重寫equals()這一條也可以。但當(dāng)hashset中元素比較多時(shí),或者是重寫的equals()方法比較復(fù)雜時(shí),我們只用equals()方法進(jìn)行比較判斷,效率也會非常低,所以引入了hashCode()這個(gè)方法,只是為了提高效率,且這是非常有必要的。比如可以這樣寫:

<pre style="margin: 0px; padding: 0px; white-space: pre-wrap; overflow-wrap: break-word; font-family: "Courier New" !important; font-size: 12px !important;">public int hashCode(){ return 1; //等價(jià)于hashcode無效
} </pre>

這樣做的效果就是在比較哈希碼的時(shí)候不能進(jìn)行判斷,因?yàn)槊總€(gè)對象返回的哈希碼都是1,每次都必須要經(jīng)過比較equals()方法后才能進(jìn)行判斷是否重復(fù),這當(dāng)然會引起效率的大大降低。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,739評論 6 534
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,634評論 3 419
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,653評論 0 377
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,063評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,835評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,235評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,315評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,459評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,000評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,819評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,004評論 1 370
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,560評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,257評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,676評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,937評論 1 288
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,717評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,003評論 2 374

推薦閱讀更多精彩內(nèi)容