關於java中的hashcode和equals方法原理
1、介紹
java編程思想和很多資料都會對自定義javabean要求必須重寫hashcode和equals方法,但並沒有清晰給出為何重寫此兩個方法,至少不是非常的明確。
首先要確定的一件事是並不是“必須”,估計跟中英文語言習慣有關。hashcode方法只有在和hash類型的集合(比如HashMap和HashSet)配合使用時才會進行調用,否則是沒有必要重寫該方法的。
所以很多人會迷惑,自己並沒有重寫這方法,程序跑起來也沒有問題。要說明這個問題必須要搞懂hashcode的真正作用以及使用場景。
2、hashcode
hashcode是java中Objet類定義的方法,默認返回的是對象的內存地址。但該方法的本意是散列。散列的話就必然涉及到在一定的空間中進行散列,所以hashcode方法一定是和集合配合使用的時候才用得到。
對象在空間散列化存儲之后,其優勢在於檢索,如果散列算法處理得好,也就是能夠保證對象在空間中盡可能均勻分布,則在檢索時,一旦確定桶的方位(即下標值),就可以排除(n-1)/n的數據量,所有在大型數據集合中,hash之后的對象檢索性能是非常高的。
java中的HashMap集合的內部實現是數組+鏈表實現,即Node[]數組方式實現的。而Node的源代碼如下:
static class Node<K,V> implements Map.Entry<K,V> {
final int hash;
final K key;
V value;
Node<K,V> next;
}
說明Node節點類是一種手拉手實現的鏈表方式,而Node[]在java編程思想中稱為桶集合,數組中每個元素可以看成一個桶。HashMap集合在初始化時會先分配16個桶的空間,在進行put操作時,會先將KV封裝成Node對象,再對key進行計算,判斷應該划分到哪個桶中。
注意key在集合中是“不重復”的,因此如果key存在,就將新的value替換掉舊的value,如果key不存在,就在鏈表的末尾進行添加。如何判斷key是否重復?HashMap類中的putVal方法源碼如下:請注意注釋地方的判斷條件是
p.hash == hash
&&((k = p.key) == key || (key != null && key.equals(k))),
首先明確p是KV構成Node對象,該對象源碼中可見含有四個屬性,分別是int hash、K key、V value和Node<K,V> next,其中hash並不是key中的hashcode,是對key的hashcode計算之后生成的新hash值,我們稱為新哈希,而生成新hash的算法是:
int newhash = (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
翻譯過來就是將舊哈希(我們稱key對象的hashcode為就hash)向右移動16位之后和自身做異或運本意就是將高16位和低16位的值進行異或運算得到一個新的數值,這么做的意圖非常明顯,高位移位運算是想讓更多的特征值參與進來,采用異或計算是想讓數據更加分散。我們知道二進制位運算中有|、&、^和~,很明顯~是單元運算,如果采用|運算記錄計算結果很大比例偏大,而采用&很大比例計算結果偏小,^運算剛好高低比例相同。如此看來,設計也是非常巧妙的。
上面的判斷條件翻譯過來,就是:如果兩個節點的新hash不同,則key一定不同。但如果新hash相同,還要判斷key是否是同一對象,若是同一對象,則說明key相同,若不是同一對象,再判斷equals方法是否相同。可以使用如下判斷語句來描述獲取更加清晰易懂:
if(newhash1 != newhash2){
//不同
}
else{
if(key1 == key2){
//相同
}
else{
if(key1.equals(key2)){
//相同
}
else{
//不同
}
}
}
桶的防止策略是對新hash對(桶數量-1)進行&運算產生的結果作為桶的下標值,由此可以看出,桶的數量一定是2的n次冪,默認是16只桶,即2^4次方,擴容以后會32,64以此類推下去。算法如下:
(n - 1) & hash //(16 - 1) & hash與取摸操作是等效的。
3、equals方法
equals方法比較簡單,就是判斷對象內容是否相同,默認實現是判斷內存地址。在java的集合中,List並不判斷對象hashcode值,只判斷equals方法。
4、總結
java集合中,HashMap和HashSet使用hashcode進行對象的散列存儲,因此會用到hashcode方法,自然也會用到equals方法,List集合只使用equals方法判斷對象是否相等。
在java的集合中,真正的集合只有數組和鏈表兩種實現,HashMap是通過兩者組合實現的,而HashSet內部是通過HashMap實現的,丟棄了HashMap中的value部分,使用了一個垃圾值(dummy)進行填充實現的。
所以究其根本,ArrayList和LinkedList應該是最基本的集合,數組列表內部封裝數組,擅長讀操作,大量寫入時(尤其是在隊首插入數據)性能較差,因為需要移動所有元素,而LinkdedList在寫入時非常有優勢,查詢則較差,兩者各有優缺點,在使用單機進行百萬數據讀寫的評測中,數組列表讀取能是列表是進10倍,而列表的寫入能力是數組列表的10倍以上,差距還是非常明顯的。