本文研究的主要是Java中hashCode的正確求值方法的相關內容,具體如下。
散列表有一項優化,可以將對象的散列碼(hashCode)緩存起來,如果散列碼不匹配,就不會檢查對象的等同性而直接認為成不同的對象。如果散列碼(hashCode)相等,才會檢測對象是否相等(equals)。
如果對象具有相同的散列碼(hashCode),他們會被映射到同一個散列桶中。如果散列表中所有對象的散列碼(hashCode)都一樣,那么該散列表就會退化為鏈表(linked list),從而大大降低其查詢效率。
一個好的散列函數通常傾向于“為不想等的對象產生不相等的散列碼”。理想情況下,散列函數應該把集合中不想等的實例均勻地分布到所有可能的散列上,但是想要完全達到這種理想的情形是非常困難的,下面給出一個相對簡單有效的散列方法:
1.把某個非零的常數值,比如說17,保存在一個名為result的int類型的變量中。
2.對于對象中的每個關鍵域f(指equals方法中涉及的每個域),完成以下步驟:
- 為該域計算int類型的散列碼c
- 如果該域是boolean類型,則計算 ( f ? 1 : 0 )
- 如果該域是byte、char、short或者int類型,則計算 ( ( int ) f )
- 如果該域是long類型,則計算 ( int ) ( f ^ ( f >>> 32 ) )
- 如果該域是float類型,則計算Float.floatToIntBits(f)
- 如果該域是double類型,則計算Double.doubleToLongBits(f),然后按照上述步驟為得到的long類型值再計算散列值
- 如果該域是一個對象引用,并且該類的equals方法通過遞歸地調用equals的方式來比較它的域,那么同樣為這個域按上述方法遞歸地調用hashCode
- 如果該域是一個數組,則要把每一個元素當作單獨的域來處理,遞歸地應用上述原則,如果數組中的每一個元素都很重要,也可以直接使用Arrays.hashCode方法。
-
按照下面的公式,把上述步驟得到的散列碼c依次合并到result中:
result = 31 * result + c;
乘法運算是為了得到一個更好的散列函數。比如如果String的散列函數省略了乘法,那么只是字母順序不同的所有字符串都會有相同的散列碼。這里之所以選擇31,是因為它是一個奇素數。如果乘數是偶數,并且乘法溢出的話,信息就會丟失,因為與2相乘等價于位移。使用素數的好處并不是很明顯,但是習慣上都使用素數來計算散列結果。31有個很好的特性,即用移位和減法來代替乘法,可以得到更好的性能:31 * i == ( i << 5 ) - i。現在的VM均可以自動實現這種優化。
如果一個類是不可變的(所有域都是final修飾,并且所有域都為基本類型或者也是不可變類),并且計算散列碼的開銷也比較大,那么就應該考慮把散列碼緩存在對象內部。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
public class HashCodeDemo { static class HashCodeClass { private final boolean bResult; private final byte byteValue; private final char charValue; private final short shortValue; private final int intValue; private final long longValue; private final float floatValue; private final double doubleValue; private final String str; private final int [] arrayValue; //volatile表示每次均在內存中去存取該變量,以保證該變量是最新的 private volatile int hashCode; public HashCodeClass() { bResult = false ; byteValue = 1 ; charValue = 'a' ; shortValue = 1 ; intValue = 1 ; longValue = 1l; floatValue = 1 .0f; doubleValue = 1 .0d; str = getClass().getName(); arrayValue = new int [] { 1 , 2 , 3 , 4 , 5 }; } @Override public int hashCode() { if (hashCode == 0 ) { // 設置一個非零的初始值,可以增加零域的沖突性 int result = 17 ; // 如果省略乘數,那么只是字母順序不同的所有字符串都會有相同的散列碼 final int HASH_CODE = 31 ; result = HASH_CODE * result + (bResult ? 1 : 0 ); result = HASH_CODE * result + byteValue; result = HASH_CODE * result + charValue; result = HASH_CODE * result + shortValue; result = HASH_CODE * result + intValue; result = HASH_CODE * result + ( int ) (longValue ^ (longValue >>> 32 )); result = HASH_CODE * result + Float.floatToIntBits(floatValue); long doubleLongValue = Double.doubleToLongBits(doubleValue); result = HASH_CODE * result + ( int ) (doubleLongValue ^ (doubleLongValue >>> 32 )); result = HASH_CODE * result + (str == null ? 0 : str.hashCode()); System.out.println( "str=" + str + ", str.hashCode=" + str.hashCode()); result = HASH_CODE * result + arrayValue.hashCode(); return result; } return hashCode; } } public static void main(String[] args) { HashCodeClass obj = new HashCodeClass(); System.out.println( "obj.hashCode=" + obj.hashCode()); System.out.println( "obj=" +obj.toString()); } } |
輸出
1
2
3
4
|
str=com.demo.test.HashCodeDemo$HashCodeClass, str.hashCode=- 205823051 obj.hashCode= 946611167 str=com.demo.test.HashCodeDemo$HashCodeClass, str.hashCode=- 205823051 obj=com.demo.test.HashCodeDemo$HashCodeClass @386c23df |
總結
以上就是本文關于淺談Java中hashCode的正確求值方法的全部內容,希望對大家有所幫助。感興趣的朋友可以繼續參閱本站其他相關專題,如有不足之處,歡迎留言指出。感謝朋友們對本站的支持!
原文鏈接:http://blog.csdn.net/chy555chy/article/details/53421055