1. 程式人生 > >Hash表及java中的equals和hashcode方法(1)

Hash表及java中的equals和hashcode方法(1)

在Java的Object類中有一個方法:
public native int hashCode();
根據這個方法的宣告可知,該方法返回一個int型別的數值,並且是本地方法,因此在Object類中並沒有給出具體的實現。 為何Object類需要這樣一個方法?它有什麼作用呢?今天我們就來具體探討一下hash表及java中的equals和hashcode方法。

一、Hash表(雜湊表)

Hash表也稱散列表,也有直接譯作雜湊表,Hash表是一種特殊的資料結構,它同陣列、連結串列以及二叉排序樹等相比較有很明顯的區別,它能夠快速定位到想要查詢的記錄,而不是與表中存在的記錄的關鍵字進行比較來進行查詢。這個源於Hash表設計的特殊性,它採用了函式對映的思想將記錄的儲存位置與記錄的關鍵字關聯起來,從而能夠很快速地進行查詢。

1、Hash表的設計思想

對於一般的線性表,比如連結串列,如果要儲存聯絡人資訊:
張三 13980593357
李四 15828662334
王五 13409821234
張帥 13890583472
那麼可能會設計一個結構體包含姓名,手機號碼這些資訊,然後把4個聯絡人的資訊存到一張連結串列中。當要查詢”李四 15828662334“這條記錄是否在這張連結串列中或者想要得到李四的手機號碼時,可能會從連結串列的頭結點開始遍歷,依次將每個結點中的姓名同”李四“進行比較,直到查詢成功或者失敗為止,這種做法的時間複雜度為O(n)。即使採用二叉排序樹進行儲存,也最多為O(logN)。假設能夠通過”李四“這個資訊直接獲取到該記錄在表中的儲存位置,就能省掉中間關鍵字比較的這個環節,複雜度直接降到O(1)。Hash表就能夠達到這樣的效果。

Hash表採用一個對映函式 f : key —> address
將關鍵字對映到該記錄在表中的儲存位置,從而在想要查詢該記錄時,可以直接根據關鍵字對映關係計算出該記錄在表中的儲存位置,通常情況下,這種對映關係稱作為Hash函式,而通過Hash函式和關鍵字計算出來的儲存位置(注意這裡的儲存位置只是表中的儲存位置,並不是實際的實體地址)稱作為Hash地址。比如上述例子中,假如聯絡人資訊採用Hash表儲存,則當想要找到“李四”的資訊時,直接根據“李四”和Hash函式計算出Hash地址即可。下面討論一下Hash表設計中的幾個關鍵問題。

2、Hash函式的設計

Hash函式設計的好壞直接影響到對Hash表的操作效率。下面舉例說明:
假如對上述的聯絡人資訊進行儲存時,採用的Hash函式為:姓名的每個字的拼音開頭大寫字母的ASCII碼之和,因此:
address(張三)=ASCII(Z)+ASCII(S)=90+83=173;
address(李四)=ASCII(L)+ASCII(S)=76+83=159;
address(王五)=ASCII(W)+ASCII(W)=87+87=174;
address(張帥)=ASCII(Z)+ASCII(S)=90+83=173;
假如只有這4個聯絡人資訊需要進行儲存,這個Hash函式設計的很糟糕。首先,它浪費了大量的儲存空間,假如採用char型陣列儲存聯絡人資訊的話,則至少需要開闢174*12位元組的空間,空間利用率只有4/174,不到5%;另外,根據Hash函式計算結果之後,address(張三)和address(李四)具有相同的地址,這種現象稱作衝突,對於174個儲存空間中只需要儲存4條記錄就發生了衝突,這樣的Hash函式設計是很不合理的。所以在構造Hash函式時應儘量考慮關鍵字的分佈特點來設計函式使得Hash地址隨機均勻地分佈在整個地址空間當中。通常有以下幾種構造Hash函式的方法: 1)直接定址法關鍵字或者關鍵字的某個線性函式為Hash地址,即address(key)=a*key+b;如知道學生的學號從2000開始,最大為4000,則可以將address(key)=key-2000作為Hash地址。
2)平方取中法 對關鍵字進行平方運算,然後取結果的中間幾位作為Hash地址。假如有以下關鍵字序列{421,423,436},平方之後的結果為{177241,178929,190096},那麼可以取{72,89,00}作為Hash地址。
3)摺疊法 將關鍵字拆分成幾部分,然後將這幾部分組合在一起,以特定的方式進行轉化形成Hash地址。假如知道圖書的ISBN號為8903-241-23,可以將address(key)=89+03+24+12+3作為Hash地址。
4)除留取餘法 如果知道Hash表的最大長度為m,可以取不大於m的最大質數p,然後對關鍵字進行取餘運算,address(key)=key%p。在這裡p的選取非常關鍵,p選擇的好的話,能夠最大程度地減少衝突,p一般取不大於m的最大質數。

3、衝突的解決

在上述例子中,發生了衝突現象,因此需要辦法來解決,否則記錄無法進行正確的儲存。通常情況下有2種解決辦法:
1)開放定址法
即當一個關鍵字和另一個關鍵字發生衝突時,使用某種探測技術在Hash表中形成一個探測序列,然後沿著這個探測序列依次查詢下去,當碰到一個空的單元時,則插入其中。比較常用的探測方法有線性探測法,比如有一組關鍵字{12,13,25,23,38,34,6,84,91},Hash表長為14,Hash函式為address(key)=key%11,當插入12,13,25時可以直接插入,而當插入23時,地址1被佔用了,因此沿著地址1依次往下探測(探測步長可以根據情況而定),直到探測到地址4,發現為空,則將23插入其中。
2)鏈地址法
採用陣列和連結串列相結合的辦法,將Hash地址相同的記錄儲存在一張線性表中,而每張表的表頭的序號即為計算得到的Hash地址。如上述例子中,採用鏈地址法形成的Hash表儲存表示為:   

雖然能夠採用一些辦法去減少衝突,但是衝突是無法完全避免的。因此需要根據實際情況選取解決衝突的辦法。
  

4、Hash表的優缺點

Hash表存在的優點顯而易見,能夠在常數級的時間複雜度上進行查詢,並且插入資料和刪除資料比較容易。但是它也有某些缺點,比如不支援排序,一般比用線性表儲存需要更多的空間,並且記錄的關鍵字不能重複。  


  

參考:http://www.cnblogs.com/dolphin0520/archive/2012/09/28/2700000.html