String 源碼探究
起因:忽然想到平時用的HashMap 當key是字符串的時候為什麽總可以覆蓋,然後看了String的源碼發現:
private final char value[];
private int hash; // Default to 0
hashCode方法:
public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h= 31 * h + val[i]; } hash = h; } return h; }
equals方法:
public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject;int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; }
很顯然hashCode和eques方法都是根據char[]數組中的char判斷的,但是hashCode函數裏面為什麽是
h = 31 * h + val[i];這個數字為什麽選擇31吶,引起了我的興趣。
下面是知乎上的回答:
The value 31 was chosen because it is an odd prime. If it were even and the multiplication overflowed, information would be lost, as multiplication by 2 is equivalent to shifting. The advantage of using a prime is less clear, but it is traditional. A nice property of 31 is that the multiplication can be replaced by a shift and a subtraction for better performance: 31 * i == (i << 5) - i. Modern VMs do this sort of optimization automatically.
設計者選擇 31 這個值是因為它是一個奇質數。如果它是一個偶數,在使用乘法當中產生數值溢出時,原有數字的信息將會丟失,因為乘以二相當於位移。
選擇質數的優勢不是那麽清晰,但是這是一個傳統。31 的一個優良的性質是:乘法可以被位移和減法替代: 31 * i == (i << 5) - i 現代的 VM 可以自行完成這個優化。
As Goodrich and Tamassia point out, If you take over 50,000 English words (formed as the union of the word lists
provided in two variants of Unix), using the constants 31, 33, 37, 39, and 41 will produce less than 7 collisions
in each case. Knowing this, it should come as no surprise that many Java implementations choose one of these constants.
Coincidentally, I was in the middle of reading the section "polynomial hash codes" when I saw this question. 正如 Goodrich 和 Tamassia 指出的那樣,如果你使用 31,33, 37,39 和 41 這幾個數值,將其應用於 hashCode 的算法中,每一個數字對超過
50000 個英語單詞(由兩個 Unix 版本的字典的並集構成)產生的 hash 只會產生少於 7 個的沖突。知道了這個之後,Java 大多數的發行版均會使用這幾個
數值之一的事實對你也不會顯得奇怪了。巧合的是,我是在閱讀『多項式哈希值』這一個章節的時候看到這個問題的。
可是為什麽java可以s="abcd"這樣直接賦值吶?難道和c語言裏面的重載一樣嗎?
但是否定的:
因為 從語言一級來看,java不支持運算符重載,這點是肯定的。
String類的”=”,”+”,”+=”,看似運算符重載,實際不是,只是在java編譯器裏做了一點手腳。
java編譯器對String的運算符做了特殊處理。
例如:
String s = “a”;
s += “b”;
編譯器轉換成了:
String s = “a”;
s = (new StringBuilder()).append(s).append(“b”).toString();
HashSet: 繼承的AbstractSet內
public int hashCode() { int h = 0; Iterator<E> i = iterator(); while (i.hasNext()) { E obj = i.next(); if (obj != null) h += obj.hashCode(); } return h; }
Integer:
public int hashCode() { return hashCode(this.value); } public static int hashCode(int var0) { return var0; }
Double:
public int hashCode() { return hashCode(this.value); } public static int hashCode(double var0) { long var2 = doubleToLongBits(var0); return (int)(var2 ^ var2 >>> 32); }
>>:帶符號右移。正數右移高位補0,負數右移高位補1
>>>:無符號右移。無論是正數還是負數,高位通通補0。
下面是關於hashCode的一些解釋:
Hash是散列的意思,就是把任意長度的輸入,通過散列算法變換成固定長度的輸出,該輸出就是散列值。關於散列值,有以下幾個關鍵結論:
1、如果散列表中存在和散列原始輸入K相等的記錄,那麽K必定在f(K)的存儲位置上
2、不同關鍵字經過散列算法變換後可能得到同一個散列地址,這種現象稱為碰撞
3、如果兩個Hash值不同(前提是同一Hash算法),那麽這兩個Hash值對應的原始輸入必定不同
HashCode
然後講下什麽是HashCode,總結幾個關鍵點:
1、HashCode的存在主要是為了查找的快捷性,HashCode是用來在散列存儲結構中確定對象的存儲地址的
2、如果兩個對象equals相等,那麽這兩個對象的HashCode一定也相同
3、如果對象的equals方法被重寫,那麽對象的HashCode方法也盡量重寫
4、如果兩個對象的HashCode相同,不代表兩個對象就相同,只能說明這兩個對象在散列存儲結構中,存放於同一個位置
String 源碼探究