- 散列函數
將整數散列最常用的方法就是除留余數法。為了均勻地散列鍵的散列值,通常都會把數組的大小取素數(HashTable 的初始大小就是 11),因為素數的因子少,余數相等的概率小,沖突的幾率就小。
HashMap 的容量始終是 2 的次冪,這是一個合數,之所以這樣設計,是為了將取模運算轉為位運算,提高性能。這個等式h % length = h & (length-1)
成立的原因如下:
2^1 = 10 2^1 -1 = 01
2^2 = 100 2^2 -1 = 011
2^3 = 1000 2^3 -1 = 0111
2^n = 1(n個零) 2^n -1 = 0(n個1)
右邊是 2^n 的二進制特點,左邊是 2^n-1 的特點,可以發現當 length = 2^n 時,h & (length-1) 的結果正好位於 0 到 length-1 之間,就相當於取模運算。
轉為位運算后,length-1 就相當於一個低位掩碼,在按位與時,它會把原散列值的高位置0,這就導致散列值只在掩碼的小范圍內變化,顯然增大了沖突幾率。為了減少沖突,HashMap 在設計散列算法時,使用高低位異或,變相的讓鍵的高位也參與了運算,代碼如下:
static final int hash(Object key) { // JDK8
int h;
// h = key.hashCode() 1. 取hashCode值
// h ^ (h >>> 16) 2. 高16位與低16位異或,變相保留高位的比特位
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
// JDK7 的源碼,JDK8 沒有這個方法,但原理一樣
static int indexFor(int h, int length) {
return h & (length-1); // 3. 取模運算
}
高位的移位異或,既能保證有效的利用鍵的高低位信息,又能減少系統開銷,這樣設計是對速度、效率和質量之間的權衡。