hashcode是用來查找的,若是你學過數據結構就應該知道,在查找和排序這一章有
例如內存中有這樣的位置
0 1 2 3 4 5 6 7
而我有個類,這個類有個字段叫ID,我要把這個類存放在以上8個位置之一,若是不用hashcode而任意存放,那麼當查找時就須要到這八個位置裏挨個去找,或者用二分法一類的算法。
但若是用hashcode那就會使效率提升不少。
咱們這個類中有個字段叫ID,那麼咱們就定義咱們的hashcode爲ID%8,而後把咱們的類存放在取得得餘數那個位置。好比咱們的ID爲9,9除8的餘 數爲1,那麼咱們就把該類存在1這個位置,若是ID是13,求得的餘數是5,那麼咱們就把該類放在5這個位置。這樣,之後在查找該類時就能夠經過ID除8 求餘數直接找到存放的位置了。 java
可是若是兩個類有相同的hashcode怎麼辦那(咱們假設上面的類的ID不是惟一的),例如9除以8和17除以8的餘數都是1,那麼這是否是合法的,回答是:能夠這樣。那麼如何判斷呢?在這個時候就須要定義 equals了。
也就是說,咱們先經過 hashcode來判斷兩個類是否存放某個桶裏,但這個桶裏可能有不少類,那麼咱們就須要再經過 equals 來在這個桶裏找到咱們要的類。
那麼。重寫了equals(),爲何還要重寫hashCode()呢?
想一想,你要在一個桶裏找東西,你必須先要找到這個桶啊,你不經過重寫hashcode()來找到桶,光重寫equals()有什麼用啊
3。你要對A類排序,有兩種方法,一種就是讓A類實現comparabole結構並實現compareTo()方法,那麼能夠經過Collections.sort(List <A> list)對其進行排序
另外一種方法:本身定義一個類B實現Comparator類並實現compare方法,
而後經過Collections.sort(List <A> list,B b)進行排序 程序員
hashCode() 是用來產生哈希瑪的,而哈希瑪是用來在散列存儲結構中肯定對象的存儲地址的,(這一段在 Java編程思想 中講的很清楚的)象util包中的 帶 hash 的集合類都是用這種存儲結構 :HashMap,HashSet, 他們在將對象存儲時(嚴格說是對象引用),須要肯定他們的地址吧, 而HashCode()就是這個用途的,通常都須要從新定義它的,由於默認狀況下,由 Object 類定義的 hashCode 方法會針對不一樣的對象返回不一樣的整數,這通常是經過將該對象的內部地址轉換成一個整數來實現的,如今舉個例子來講, 就拿HashSet來講 ,在將對象存入其中時,經過被存入對象的 hashCode() 來肯定對象在 HashSet 中的存儲地址,經過equals()來肯定存入的對象是否重複,hashCode() ,equals()都須要本身從新定義,由於hashCode()默認前面已經說啦,而equals() 默認是比較的對象引用,你如今想一下,若是你不定義equals()的話,那麼同一個類產生的兩個內容徹底相同的對象均可以存入Set,由於他們是經過 equals()來肯定的,這樣就使得HashSet 失去了他的意義,看一下下面這個: 算法
public class Test {
public static void main(String[] args) {
HashSet set = new HashSet();
for (int i = 0; i <= 3; i++){
set.add(new Demo1(i,i));
}
System.out.println(set);
set.add(new Demo1(1,1));
System.out.println(set);
System.out.println(set.contains(new Demo1(0,0)));
System.out.println(set.add(new Demo1(1,1)));
System.out.println(set.add(new Demo1(4,4)));
System.out.println(set);
}
private static class Demo1 {
private int value;
private int id;
public Demo1(int value,int id) {
this.value = value;
this.id=id;
}
public String toString() {
return " value = " + value;
}
public boolean equals(Object o) {
Demo1 a = (Demo1) o;
return (a.value == value) ? true : false;
}
public int hashCode() {
return id;
}
}
}
你分別註釋掉hashCode()和 equals()來比較一下他們做用就能夠拉,關鍵要本身動手看看比較的結果你就能夠記得很清楚啦
若是還不是很明確能夠再看另外一個例子: shell
public final class Test {
public static void main(String[] args) {
Map m = new HashMap();
m.put(new PhoneNumber(020, 12345678), "shellfeng");
System.out.println(m.get(new PhoneNumber(020, 12345678)));
}
private static class PhoneNumber {
/**
* 區號
*/
private short areaCode;
/**
* 擴展號
*/
private short extension;
public PhoneNumber(int areaCode, int extension) {
this.areaCode = (short) areaCode;
this.extension = (short) extension;
}
public boolean equals(Object o) {
if (o == this) {
return true;
}
if (!(o instanceof PhoneNumber)) {
return false;
}
PhoneNumber pn = (PhoneNumber) o;
return pn.extension == extension && pn.areaCode == areaCode;
}
/**
* @see java.lang.Object#hashCode()
* @return
* result就是咱們獲得的散列值,其實咱們的計算過程能夠多種,這裏只不過是一個例子,須要你的靈活運用,使其接近你須要的理想結果
*/
public int hashCode() {
int result = 17;
result = 37 * result + areaCode;
result = 37 * result + extension;
return result;
}
}
}
仍是那句話:你註釋掉hashCode()比較一下他們做用就能夠拉,關鍵要本身動手看看比較的結果你就能夠記得很清楚啦
總結
hashCode() 方法使用來提升Map裏面的搜索效率的,Map會根據不一樣的hashCode()來放在不一樣的桶裏面,Map在搜索一個對象的時候先經過 hashCode()找到相應的桶,而後再根據equals()方法找到相應的對象.要正確的實現Map裏面查找元素必須知足一下兩個條件:
(1)當obj1.equals(obj2)爲true時obj1.hashCode() == obj2.hashCode()必須爲true
(2)當obj1.hashCode() == obj2.hashCode()爲false時obj.equals(obj2)必須爲false
Java中的集合(Collection)有兩類,一類是List,再有一類是Set。你知道它們的區別嗎?前者集合內的元素是有序的,元素能夠重複;後者元素無序,但元素不可重複。
那麼這裏就有一個比較嚴重的問題了:要想保證元素不重複,可兩個元素是否重複應該依據什麼來判斷呢?這就是Object.equals方法了。
可是,若是每增長一個元素就檢查一次,那麼當元素不少時,後添加到集合中的元素比較的次數就很是多了。
也就是說,若是集合中如今已經有1000個元素,那麼第1001個元素加入集合時,它就要調用1000次equals方法。這顯然會大大下降效率。
哈 希算法也稱爲散列算法,是將數據依特定算法直接指定到一個地址上。咱們能夠認爲hashCode方法返回的就是對象存儲的物理地址(實際可能並非,例 如:經過獲取對象的物理地址而後除以8再求餘,餘數幾是計算獲得的散列值,咱們就認爲返回一個不是物理地址的數值,而是一個能夠映射到物理地址的值)。
這 樣一來,當集合要添加新的元素時,先調用這個元素的hashCode方法,就一會兒能定位到它應該放置的物理位置上。若是這個位置上沒有元素,它就能夠直 接存儲在這個位置上,不用再進行任何比較了;若是這個位置上已經有元素了,就調用它的equals方法與新元素進行比較,相同的話就不存了,不相同就散列 其它的地址。因此這裏存在一個衝突解決的問題。這樣一來實際調用equals方法的次數就大大下降了,幾乎只須要一兩次。
改寫equals時老是要改寫hashCode
============================================================
java.lnag.Object中對hashCode的約定:
1. 在一個應用程序執行期間,若是一個對象的equals方法作比較所用到的信息沒有被修改的話,則對該對象調用hashCode方法屢次,它必須始終如一地返回同一個整數。
2. 若是兩個對象根據equals(Object o)方法是相等的,則調用這兩個對象中任一對象的hashCode方法必須產生相同的整數結果。
3. 若是兩個對象根據equals(Object o)方法是不相等的,則調用這兩個對象中任一個對象的hashCode方法,不要求產生不一樣的整數結果。但若是能不一樣,則可能提升散列表的性能。
有一個概念要牢記,兩個相等對象的equals方法必定爲true, 但兩個hashcode相等的對象不必定是相等的對象。
因此hashcode相等只能保證兩個對象在一個HASH表裏的同一條HASH鏈上,繼而經過equals方法才能肯定是否是同一對象,若是結果爲true, 則認爲是同一對象不在插入,不然認爲是不一樣對象繼續插入。
Object的代碼: 編程
public String toString() {
return this.getClass().getName() + "@"+ Integer.toHexString(this.hashCode());
}
public boolean equals(Object o) {
return this == o;
}
public native int hashCode();
從上面咱們能夠看到是否極可能Object.hashCode就是表明內存地址。下面咱們來證實hashcode是否是真的就是Object的內存地址呢?實際上,hashcode根本不能表明object的內存地址。
-----------------------------------------
Object.hashCode不能夠表明內存地址
----------------------------------------
/**
* 此方法的做用是證實 java.lang.Object的hashcode 不是表明 對象所在內存地址。
* 我產生了10000個對象,這10000個對象在內存中是不一樣的地址,可是實際上這10000個對象 的hashcode的是徹底可能相同的
*/
public class HashCodeMeaning {
public static void main(String[] args) {
ArrayList list = new ArrayList();
int numberExist = 0;
// 證實hashcode的值不是內存地址
for (int i = 0; i < 10000; i++) {
Object obj = new Object();
if (list.contains(obj.toString())) {
System.out
.println(obj.toString() + " exists in the list. " + i);
numberExist++;
} else {
list.add(obj.toString());
}
}
System.out.println("repetition number:" + numberExist);
System.out.println("list size:" + list.size());
// 證實內存地址是不一樣的。
numberExist = 0;
list.clear();
for (int i = 0; i < 10000; i++) {
Object obj = new Object();
if (list.contains(obj)) {
System.out.println(obj + " exists in the list. " + i);
numberExist++;
} else {
list.add(obj);
}
}
System.out.println("repetition number:" + numberExist);
System.out.println("list size:" + list.size());
}
}
==============================
看HashTable的源代碼很是有用:
==============================
============================================================
有效和正肯定義hashCode()和equals():
============================================================
級別:入門級
每一個Java對象都有hashCode()和 equals()方法。許多類忽略(Override)這些方法的缺省實施,以在對象實例之間提供更深層次的語義可比性。在Java理念和實踐這一部分, Java開發人員Brian Goetz向您介紹在建立Java類以有效和準肯定義hashCode()和equals()時應遵循的規則和指南。您能夠在討論論壇與做者和其它讀者一 同探討您對本文的見解。(您還能夠點擊本文頂部或底部的討論進入論壇。)
雖然Java語言不直接支持關聯數組 -- 可使用任何對象做爲一個索引的數組 -- 但在根Object類中使用hashCode()方法明確表示指望普遍使用HashMap(及其前輩Hashtable)。理想狀況下基於散列的容器提供 有效插入和有效檢索;直接在對象模式中支持散列能夠促進基於散列的容器的開發和使用。
定義對象的相等性
Object類有兩種方法來推斷對象的標識:equals()和hashCode()。通常來講,若是您忽略了其中一種,您必須同時忽略這兩種,由於二者 之間有必須維持的相當重要的關係。特殊狀況是根據equals() 方法,若是兩個對象是相等的,它們必須有相同的hashCode()值(儘管這一般不是真的)。
特定類的equals()的語義在Implementer的左側定義;定義對特定類來講equals()意味着什麼是其設計工做的一部分。Object提供的缺省實施簡單引用下面等式:
public boolean equals(Object obj) { return (this == obj); }
在這種缺省實施狀況下,只有它們引用真正同一個對象時這兩個引用纔是相等的。一樣,Object提供的 hashCode()的缺省實施經過將對象的內存地址對映於一個整數值來生成。因爲在某些架構上,地址空間大於int值的範圍,兩個不一樣的對象有相同的 hashCode()是可能的。若是您忽略了hashCode(),您仍舊可使用System.identityHashCode()方法來接入這類缺 省值。
忽略 equals() -- 簡單實例
缺省狀況下,equals()和hashCode()基於標識的實施是合理的,但對於某些類來講,它們但願放寬等式的定義。例如,Integer類定義equals() 與下面相似:
public boolean equals(Object obj) {
return (obj instanceof Integer
&& intValue() == ((Integer) obj).intValue());
}
在這個定義中,只有在包含相同的整數值的狀況下這兩個Integer對象是相等的。結合將不可修改的 Integer,這使得使用Integer做爲HashMap中的關鍵字是切實可行的。這種基於值的Equal方法能夠由Java類庫中的全部原始封裝類 使用,如Integer、Float、Character和Boolean以及String(若是兩個String對象包含相同順序的字符,那它們是相等 的)。因爲這些類都是不可修改的而且能夠實施hashCode()和equals(),它們均可以作爲很好的散列關鍵字。
爲何忽略 equals()和hashCode()?
若是Integer不忽略equals() 和 hashCode()狀況又將如何?若是咱們從未在HashMap或其它基於散列的集合中使用Integer做爲關鍵字的話,什麼也不會發生。可是,若是 咱們在HashMap中使用這類Integer對象做爲關鍵字,咱們將不可以可靠地檢索相關的值,除非咱們在get()調用中使用與put()調用中極其 相似的Integer實例。這要求確保在咱們的整個程序中,只能使用對應於特定整數值的Integer對象的一個實例。不用說,這種方法極不方便並且錯誤 頻頻。
Object的interface contract要求若是根據 equals()兩個對象是相等的,那麼它們必須有相同的hashCode()值。當其識別能力整個包含在equals()中時,爲何咱們的根對象類需 要hashCode()?hashCode()方法純粹用於提升效率。Java平臺設計人員預計到了典型Java應用程序中基於散列的集合類 (Collection Class)的重要性--如Hashtable、HashMap和HashSet,而且使用equals()與許多對象進行比較在計算方面很是昂貴。使所 有Java對象都可以支持 hashCode()並結合使用基於散列的集合,能夠實現有效的存儲和檢索。
==============================
Go deep into HashCode:
==============================
爲何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來作映射呢(爲了一些思惟有障礙的人能看到懂我加了一句話:而不是直接放value呢)?這就是關係Hashtable性能問題的最重要的問題:Hash衝突.
常見的Hash衝突是不一樣對象最終產生了相同的索引,而一種很是甚至絕對少見的Hash衝突
是,若是一組對象的個數大過了int範圍,而HashCode的長度只能在int範圍中,因此確定要
有同一組的元素有相同的HashCode,這樣不管如何他們都會有相同的索引.固然這種極端
的狀況是極少見的,能夠暫不考慮,但對於相同的HashCode通過取模,則會產中相同的索引,
或者不一樣的對象卻具備相同的HashCode,固然具備相同的索引.
因此對於索引相同的對象,在該index位置存放了多個對象,這些值要想能正確區分,就要依
靠key自己和hashCode來識別.
事實上一個設計各好的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() + Attribute2.HashCode()...[+super.HashCode()],
咱們知道,每次調用這個方法,都要從新對方法內的參與散列的對象從新計算一次它們的
HashCode的運算,若是一個對象的屬性沒有改變,仍然要每次都進行計算,因此若是設置一
個標記來緩存當前的散列碼,只要當參與散列的對象改變時才從新計算,不然調用緩存的
hashCode,這能夠從很大程度上提升性能.
默認的實現是將對象內部地址轉化爲整數做爲HashCode,這固然能保證每一個對象具備不一樣
的HasCode,由於不一樣的對象內部地址確定不一樣(廢話),但java語言並不能讓程序員獲取對
象內部地址,因此,讓每一個對象產生不一樣的HashCode有着不少可研究的技術.