首先,我們知道 HashMap 的底層實現是開放地址法 + 鏈地址法的方式來實現。
即陣列 + 連結串列的實現方式,通過計算雜湊值,找到陣列對應的位置,如果已存在元素,就加到這個位置的連結串列上。在 Java 8 之後,連結串列過長還會轉化為紅黑樹。
這個陣列並不是一開始就很大,而是隨著 HashMap 裡面的值變多,達到 LoadFactor 的界限之後,就會擴容。剛開始的陣列很小,預設只有 16。
這個陣列大小一定是 2 的 n 次方,因為找到陣列對應的位置需要通過取餘計算,取餘計算是一個很耗費效能的計算,而對 2 的 n 次方取餘就是對 2 的 n 次方減一取與運算。所以保持陣列大小為 2 的 n 次方,這樣就可以保證計算位置高效。
那麼這個雜湊值究竟是怎麼計算的呢?假設就是用 Key 的雜湊值直接計算。假設有如下兩個 key,雜湊值分別是:
key1:
0000 0000 0010 1111 1001 0000 0110 1101
key2:
0000 0000 0010 0000 1001 0000 0110 1101
如果直接使用陣列預設大小,取餘之後 key1 與 key2 就會到陣列同一個下標。其實 key1 和 key2 的高位是不一樣的。
由於陣列是從小到達擴容的,為了優化高位被忽略這個問題,HashMap 原始碼中對於計算雜湊值做了優化,採用高位16位組成的數字與源雜湊值取異或而生成的雜湊值作為用來計算 HashMap 的陣列位置的雜湊值:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
為什麼要用異或?首先,對於一個數字,轉換成二進位制之後,其中為的 1 的位置代表這個數字的特性.對於異或運算,如果a、b兩個值不相同,則異或結果為1。如果a、b兩個值相同,異或結果為0。0與0異或是0,0與1異或是1,這樣相當於讓高位的特性在低位得以體現,所以採用這種演算法,減少碰撞。
微信搜尋“我的程式設計喵”關注公眾號,每日一刷,輕鬆提升技術,斬獲各種offer: