1. 程式人生 > >除數為2的N次方取模可以用與運算替代,效率更高

除數為2的N次方取模可以用與運算替代,效率更高

 取模運算在包括JAVA在內的大多數語言中的效率都十分低下,而當除數為2的N次方時,取模運算將退化為最簡單的位運算,其效率明顯提升(按照Bruce Eckel給出的資料,大約可以提升5~8倍) 。看看JDK中是如何實現的: 

Java程式碼:
  1. staticint indexFor(int h, int length) {   
  2. return h & (length-1);   
  3. }  
  1. staticint indexFor(int h, int length) {  
  2. return h & (length-1);  
  3. }  


當key空間長度為2的N次方時,計算hashCode為h的元素的索引可以用簡單的與操作來代替笨拙的取模操作!假設某個物件的hashCode為35(二進位制為100011),而hashMap採用預設的initialCapacity(16),那麼indexFor計算所得結果將會是100011 & 1111 = 11,即十進位制的3,是不是恰好是35 Mod 16。 


上面的方法有一個問題,就是它的計算結果僅有物件hashCode的低位決定,而高位被統統遮蔽了;以上面為例,19(10011)、35(100011)、67(1000011)等就具有相同的結果。針對這個問題, Joshua Bloch採用了“防禦性程式設計”的解決方法,在使用各物件的hashCode之前對其進行二次Hash,參看JDK中的原始碼: 

  1. staticint hash(Object x) {   
  2. int h = x.hashCode();   
  3.         h += ~(h << 9);   
  4.         h ^=  (h >>> 14);   
  5.         h +=  (h << 
    4);   
  6.         h ^=  (h >>> 10);   
  7. return h;   
  8.     }  
  1. staticint hash(Object x) {  
  2. int h = x.hashCode();  
  3. h += ~(h << 9);  
  4. h ^=  (h >>> 14);  
  5. h +=  (h << 4);  
  6. h ^=  (h >>> 10);  
  7. return h;  
  8. }  


採用這種旋轉Hash函式的主要目的是讓原有hashCode的高位資訊也能被充分利用,且兼顧計算效率以及資料統計的特性,其具體的原理已超出了本文的領域。 


加快Hash效率的另一個有效途徑是編寫良好的自定義物件的HashCode,String的實現採用瞭如下的計算方法: 

  1. for (int i = 0; i < len; i++) {   
  2. h = 31*h + val[off++];   
  3. }   
  4. hash = h;  
  1. for (int i = 0; i < len; i++) {  
  2. h = 31*h + val[off++];  
  3. }  
  4. hash = h;  


這種方法HashCode的計算方法可能最早出現在Brian W. Kernighan和Dennis M. Ritchie的《The C Programming Language》中,被認為是價效比最高的演算法(又被稱為times33演算法,因為C中乘數常量為33,JAVA中改為31),實際上,包括List在內的大多數的物件都是用這種方法計算Hash值。