前言
前面我們討論了HashMap的結構, 接下來幾篇我們從原始碼角度來看HashMap的實現細節.
本篇我們就來聊聊HashMap的hash演算法
本文的原始碼基於 jdk8 版本.
hash演算法
上一篇文章我們提到, 為了利用陣列索引進行快速查詢, 我們需要先將 key
值對映成陣列下標. 因為陣列的下標是有限的集合, 所以我們可以先通過hash演算法將key
對映成整數, 再將整數對映成有限的陣列下標:
Object -> int -> index
對於 Object -> int
部分, 使用的就是hash function, 而對於 int -> index
部分, 我們可以簡單的使用對陣列大小取模來實現.
下面我們就來看看HashMap使用了什麼hash演算法.
首先我們來看維基百科對於hash function的定義:
雜湊函式(英語:Hash function)又稱雜湊演算法、雜湊函式,是一種從任何一種資料中建立小的數字“指紋”的方法。雜湊函式把訊息或資料壓縮成摘要,使得資料量變小,將資料的格式固定下來。該函式將資料打亂混合,重新建立一個叫做雜湊值(hash values,hash codes,hash sums,或hashes)的指紋。
在java中, hash函式是一個native方法, 這個定義在Object類中, 所以所有的物件都會繼承.
public native int hashCode();
因為這是一個本地方法, 所以我們無法看到它的具體實現, 但是從函式簽名上可以看出, 該方法將任意物件對映成一個整型值.呼叫該方法, 我們就完成了 Object -> int
的對映
所以將 key
對映成index
的方式可以是
key.hashCode() % table.length
那麼HashMap是這樣做的嗎? 事實上, HashMap定義了自己的雜湊方法:
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
我們知道, int型別是32位的, h ^ h >>> 16
其實就是將hashCode的高16位和低16位進行異或, 這充分利用了高半位和低半位的資訊, 對低位進行了擾動
, 目的是為了使該hashCode對映成陣列下標時可以更均勻, 詳細的解釋可以參考這裡.
另外, 從這個函式中, 我們還可以得到一個意外收穫:
HashMap中key值可以為null, 且null值一定儲存在陣列的第一個位置.
效能提升
前面我們提到, 將hash值轉換成陣列下標我們可以採用取模運算, 但是取模運算是十分耗時的.
另一方面, 我們知道, 當一個數是 2^n 時, 任意整數對2^n取模等效於:
h % 2^n = h & (2^n -1)
這樣我們就將取模操作轉換成了位操作, 而位操作的速度遠遠快於取模操作.
為此, HashMap中, table的大小都是2的n次方, 即使你在建構函式中指定了table的大小, HashMap也會將該值擴大為距離它最近的2的整數次冪的值. 這在我們下面分析建構函式的時候就能看到了.
(完)
下一篇 : 深入理解HashMap(三): 關鍵原始碼逐行分析之建構函式
檢視更多系列文章:系列文章目錄