1. 程式人生 > >HashMap原始碼之hash()函式分析(JDK 1.8)

HashMap原始碼之hash()函式分析(JDK 1.8)

    我們知道,使用雜湊的容器,其高效能的主要影響因素之一就是hash值。

    在HashMap中,為了更好的效能,我們希望作為Key的物件提供一個合理的hash函式以便能將其合理的分配到桶中。

    而在實際的HashMap中,對從物件獲取的hash值又做了調整。

    我們先看原始碼:

  1. staticfinalint hash(Object key){
  2. int h;
  3. return(key ==null)?0:(h = key.hashCode())^(h >>>16);
  4. }

    我們可以將其詳細步驟等價改為如下:

  1. staticfinalint hash(Object
    key){
  2. if(key ==null)
  3. return0;
  4. int h = key.hashCode();
  5. int temp = h >>>16;
  6. int newHash = h ^ temp;
  7. return newHash;
  8. }

    程式碼過程的直接翻譯就是:如果Key值為null,返回0;如果Key值不為空,返回原hash值和原hash值無符號右移16位的值按位異或的結果。

    我們知道,按位異或就是把兩個數按二進位制,相同就取0,不同就取1。

    比如:0101 ^ 1110 的結果為 1011。(記得以前上數位電路課的時候學過)異或的速度是非常快的。

    把一個數右移16位即丟棄低16為,就是任何小於216

的數,右移16後結果都為0(2的16次方再右移剛好就是1)。

    任何一個數,與0按位異或的結果都是這個數本身(很好驗證)。

    所以這個hash()函式對於非null的hash值,僅在其大於等於216的時候才會重新調整其值。

    但是調整後又什麼好處呢?

    我們先看下put的時候,這個hash值是怎麼處理(部分原始碼)的:

  1. public V put(K key, V value){
  2. return putVal(hash(key), key, value,false,true);
  3. }
  4. final V putVal(int hash, K key, V value,boolean
    onlyIfAbsent,boolean evict){
  5. Node<K,V>[] tab;Node<K,V> p;int n, i;
  6. if((tab = table)==null||(n = tab.length)==0)
  7. n =(tab = resize()).length;
  8. if((p = tab[i =(n -1)& hash])==null)
  9. tab[i]= newNode(hash, key, value,null);
  10. ......
  11. }

    在尋找桶位的時候,這個hash值為與上table的zise-1,初始為16,我們就拿16來舉例.

    以為演算法是hashValue & size - 1 ,此時size-1=15的二進位制為 1 1 1 1 ,也就是任意類似16進位制0x?0(二進位制最後四位為0000)的hash值,都會被儲存到位置為0的桶位上,一個桶中的元素太多,就一定會降低其效能,但是我們來看看這樣的hash值經過上面的函式處理過後的結果:

  1. publicclassTestHashCodeMethod{
  2. publicstaticvoid main(String args[])throwsException{
  3. finalint max =Integer.MAX_VALUE >>>4;
  4. Random random =newRandom(System.currentTimeMillis());
  5. for(int i=0;i<20;i++){
  6. int hash = random.nextInt(max)<<4;
  7. int betterHash = hash ^(hash >>>16);
  8. System.out.print(toBinaryString(hash));
  9. System.out.println("-->"+ toBinaryString(betterHash));
  10. }
  11. }
  12. //將整數轉換為二進位制字串,高位補0
  13. finalstaticchar[] digits ={'0','1'};
  14. staticString toBinaryString(int i){
  15. char[] buf =newchar[32];
  16. int pos =32;
  17. int mask =1;
  18. do{
  19. buf[--pos]= digits[i & mask];
  20. i >>>=1;
  21. }while(pos >0);
  22. returnnewString(buf, pos,32);
  23. }
  24. }

    檢視結果該hash函式轉換後的值:

  1. 00011000000100011111000101100000-->00011000000100011110100101110001
  2. 00110111100110001011010000100000-->00110111100110001000001110111000
  3. 01101110000011101111100011010000-->01101110000011101001011011011110
  4. 00000000000111110010101100010000-->00000000000111110010101100001111
  5. 00110101101001101000010010010000-->00110101101001101011000100110110
  6. 00101111111111001011000101010000-->00101111111111001001111010101100
  7. 01100101111101101110100110110000-->01100101111101101000110001000110
  8. 00000011101101101110000110100000-->00000011101101101110001000010110
  9. 00100011001101010011110010110000-->00100011001101010001111110000101
  10. 01101101111010000001111011110000-->01101101111010000111001100011000
  11. 01111001111100110101000101010000-->01111001111100110010100010100011
  12. 00111110101001111101100110100000-->00111110101001111110011100000111
  13. 01011001000001101010011001110000-->01011001000001101111111101110110
  14. 01101000101100101110101100100000-->01101000101100101000001110010010
  15. 01100110111011001111110001000000-->01100110111011001001101010101100
  16. 00100001100011000010110001100000-->00100001100011000000110111101100
  17. 01100010001010000110101111110000-->01100010001010000000100111011000
  18. 00000011001111101111111110110000-->00000011001111101111110010001110
  19. 00111110100100101011111110110000-->00111110100100101000000100100010
  20. 01000100000101011111111110000000-->01000100000101011011101110010101

    是不是發現情況都發生了好轉,原來一大批會被放到“0”桶位的hash值,現在幾乎都被更佳合理的分配到了其他桶位。

    我們知道hashMap中的桶位都是以oldCap<<1(即原容量*2)來增長的,所以最終這個hash值要存放的時候,都是跟一連串二進位制的“1"作與運算的,而容量定義為int型別,java中int型別為4位元組,即32位,但是Integer.MAX為0x7fffffff,也就是231 - 1 那麼大(因為最高位被用作符號位),而取16算是一種折衷的辦法。而另一個原因,也許是跟物件本身的hash值(當然也為int)有關。

    那麼這個方法就介紹這麼多了,近期準備將HashMap整個原始碼解讀一下,並分享出來,並在最終整體介紹一下Java的容器體系。

    之前已發過兩篇容器的原始碼解讀,這裡給出連結: