關於HashCode與其係數31

 首先我們來了解一下hashcode,什麼是hashcode?有什麼作用?

hashcode其實就是散列碼,hashcode使用高效率的哈希算法來定位查找對象!

我們在使用容器來存儲數據的時候會計算一串散列碼,然後將數據放入容器。

如:String s =“java”,那麼計算機會先計算散列碼,然後放入相應的數組中,數組的索引就是從散列碼計算來的,然後再裝入數組裏的容器裏,如List.這就相當於把你要存的數據分成了幾個大的部分,然後每個部分存了很多值, 你查詢的時候先查大的部分,再在大的部分裏面查小的,這樣就比先行查詢要快很多!

一個對象的HashCode就是一個簡單的Hash算法的實現,雖然它和那些真正的複雜的Hash算法相比還不能叫真正的算法,但如何實現它,不僅僅是程序員的編程水平問題, 而是關係到你的對象在存取時性能的非常重要的問題.有可能,不同HashCode可能 會使你的對象存取產生成百上千倍的性能差別!

java String在打印這個類型的實例對象的時候總是顯示爲下面的形式

test.Test$tt@c17164

上面test.Test是類名$tt是我自己寫的內部類,而@後面這一段是什麼呢?他其實就是tt這個實例類的hashcode的16進制!

它使用了Object 裏面的toString()方法

      Java代碼:
  1. return getClass().getName() + “@” + Integer.toHexString(hashCode());  
繼續看看java裏 String hashcode的源碼:
  1. public int hashCode() {  
  2.     int h = hash;  
  3.     if (h == 0 && value. length > 0) {  
  4.         char val[] = value;  
  5.   
  6.         for ( int i = 0; i < value. length; i++) {  
  7.             h = 31 * h + val[i];  
  8.         }  
  9.         hash = h;  
  10.     }  
  11.     return h;  
  12. }  
save_snippets.png
    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;
    }



其實上面的實現也就是數學表達式的實現:

Java代碼 
  1. s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s[n-1]  
      s[i]是string的第i個字符,n是String的長度。
這個怎麼算出來的呢?
看一下這個步驟吧(參考上面的源碼,進行for循環,然後寫下每步):
  1. String str = "abcd";  
  2.   
  3. h = 0  
  4. value.length = 4  
  5.   
  6. val[0] = a  
  7. val[1] = b  
  8. val[2] = c  
  9. val[3] = d  
  10.   
  11. h = 31*0 + a  
  12.   = a  
  13.   
  14. h = 31 * (31*0 + a) + b  
  15.   = 31 * a + b  
  16.   
  17. h = 31 * (31 * (31*0 + a) + b) + c  
  18.   = 31 * (31 * a + b) + c  
  19.   = 31 * 31 * a + 31 * b + c  
  20.   
  21. h = 31 * (31 * 31 * a + 31 * b + c) + d  
  22.   = 31 * 31 * 31 * a + 31 * 31 * b + 31 * c + d  
  23.    
  24. h = 31 ^ (n-1) * val[0] + 31 ^ (n-2) * val[1] + 31 ^ (n-3) * val[2] + ...+ val[n-1]  
save_snippets.png
String str = "abcd";

h = 0
value.length = 4

val[0] = a
val[1] = b
val[2] = c
val[3] = d

h = 31*0 + a
  = a

h = 31 * (31*0 + a) + b
  = 31 * a + b

h = 31 * (31 * (31*0 + a) + b) + c
  = 31 * (31 * a + b) + c
  = 31 * 31 * a + 31 * b + c

h = 31 * (31 * 31 * a + 31 * b + c) + d
  = 31 * 31 * 31 * a + 31 * 31 * b + 31 * c + d
 
h = 31 ^ (n-1) * val[0] + 31 ^ (n-2) * val[1] + 31 ^ (n-3) * val[2] + ...+ val[n-1]



我們會注意那個狗血的31這個係數爲什麼總是在裏面乘來乘去?爲什麼不適用32或者其他數字?

大家都知道,計算機的乘法涉及到移位計算。當一個數乘以2時,就直接拿該數左移一位即可!選擇31原因是因爲31是一個素數!

所謂素數:

質數又稱素數。指在一個大於1的自然數中,除了1和此整數自身外,沒法被其他自然數整除的數。

在存儲數據計算hash地址的時候,我們希望儘量減少有同樣的hash地址,所謂“衝突”。如果使用相同hash地址的數據過多,那麼這些數據所組成的hash鏈就更長,從而降低了查詢效率!所以在選擇係數的時候要選擇儘量長(31 = 11111[2])的係數並且讓乘法儘量不要溢出(如果選擇大於11111的數,很容易溢出)的係數,因爲如果計算出來的hash地址越大,所謂的“衝突”就越少,查找起來效率也會提高。

31可以 由i*31== (i<<5)-1來表示,現在很多虛擬機裏面都有做相關優化,使用31的原因可能是爲了更好的分配hash地址,並且31只佔用5bits!

在java乘法中如果數字相乘過大會導致溢出的問題,從而導致數據的丟失.

而31則是素數(質數)而且不是很長的數字,最終它被選擇爲相乘的係數的原因不過與此!



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章