標籤:
Writer:BYSocket(泥沙磚瓦漿木匠)
微博:BYSocket
豆瓣:BYSocket
一、前言
泥瓦匠最近被項目搞的天昏地暗。發現有些要給自己一些目標,關於技術的目標:
專註很重要。專註Java 基礎 + H5(學習)
其他動作系統,演算法,資料結構當成課外書博覽。有時候,就是那樣你越是專註方面越多對自己打擊越大學啥啥都不好。今天帶來Java基礎:hashCode方法
二、hashCode方法
hash code(散列碼,也可以叫雜湊碼值)是對象產生的一個整型值。其產生沒有規律的。二者散列碼可以擷取對象中的資訊,轉成那個對象的“相對唯一”的整型值。所有對象都有一個散列碼,hashCode()是根類 Object 的一個方法。散列表的工作原理在Java基礎不展開講,只要知道它是一種快速的“字典”即可。下面引用老外一張圖:
三、兩個小例子
首先泥瓦匠引用一段來自 Object規範 【JavaSE6】:
hashCode的常規協定是:
- 1、在 Java 應用程式執行期間,在對同一對象多次調用 hashCode 方法時,必須一致地返回相同的整數,前提是將對象進行 equals 比較時所用的資訊沒有被修改。從某一應用程式的一次執行到同一應用程式的另一次執行,該整數無需保持一致。
- 2、如果根據 equals(Object) 方法,兩個對象是相等的,那麼對這兩個對象中的每個對象調用
hashCode 方法都必鬚生成相同的整數結果。
- 3、如果根據equals方法,兩個對象不相等,那麼對這兩個對象中的任一對象上調用 hashCode 方法不 要求一定產生不同的整數結果。但是,程式員應該意識到,為不相等的對象產生不同整數結果可以提高雜湊表的效能。
- 由於hashCode定義在根類Object,所以每個對象都是Object,都具有一個預設的散列值,即是對象的儲存地址。泥瓦匠請大家看一下這個例子:
?
| 1234567891011121314151617 |
public class HashCodeTest{ public static void main(String[] args) { String s = "hashCode"; StringBuilder sb = new StringBuilder(s); System.out.println("hashCode1: " + s.hashCode() + " " + sb.hashCode()); String s1 = new String("hashCode"); StringBuilder sb1 = new StringBuilder(s1); System.out.println("hashCode2: " + s1.hashCode() + " " + sb1.hashCode()); // are they equals? System.out.println("s s1 : " + s.equals(s1)); System.out.println("sb sb1: " + sb.equals(sb1)); }} |
run 一下,可以在控制台看到:
?
| 1234 |
hashCode1: 147696667 1385112968hashCode2: 147696667 870919696s s1 : truesb sb1: false |
泥瓦匠小結:
1、s 與 s1相等,且hashCode一樣。驗證了【hashCode的常規協定】的第二條。原因是字串的散列碼由內容匯出的。(這個第二個例子我們會驗證)
2、StringBuilder 裡面沒有定義hashCode方法,所以匯出的是Object預設的對Object Storage Service的地址。(注意到Object的hashCode方法前面有個native的修飾符,這表示hashCode方法是由非java語言實現的,具體的方法實現在外部,返回記憶體對象的地址。)詳情請看認識&理解關鍵字 native 實戰篇。
泥瓦匠剛剛提到字串散列碼是由內容匯出的。下面看看String的hashCode的實現。
?
| 12345678910111213141516171819202122232425 |
/** The value is used for character storage */private char value[]; private int hash;// Default to 0 /** * Returns a hash code for this string. The hash code for a * String object is computed as * s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1] */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;} |
泥瓦匠小結:
1、s[0]*31^(n-1) + s[1]*31^(n-2) + … + s[n-1] 數學公式代表什嗎?
s[i]是string的第i個字元,n是String的長度。31為啥呢?下面引用《Effective Java》的原話:
之所以選擇31,是因為它是個奇素數,如果乘數是偶數,並且乘法溢出的話,資訊就會丟失,因為與2相乘等價於移位元運算。使用素數的好處並不是很明顯,但是習慣上都使用素數來計算散列結果。31有個很好的特性,就是用移位和減法來代替乘法,可以得到更好的效能:31*i==(i<<5)-i。現在的VM可以自動完成這種最佳化。
四、結論和忠告
確實hashCode有點晦澀,有可能是因為那個數學散列函數。下面是《Effective Java》中的結論點:
1、如果對象有相同的散列碼,被映射到同一個散列桶,這樣散列表退化稱為 鏈表 ,這樣效能降低。
2、相等的對象必須具有相等的散列碼
3、為不相等的對象產生不相等的散列碼
4、不要試圖從散列碼計算中排除掉一個對象關鍵區段來提高效能
Writer:BYSocket(泥沙磚瓦漿木匠)
微博:BYSocket
豆瓣:BYSocket
Java 基礎:hashCode方法