深入理解HashMap(二): 關鍵原始碼逐行分析之hash演算法

ChiuCheng發表於2019-01-19

前言

系列文章目錄

前面我們討論了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(三): 關鍵原始碼逐行分析之建構函式

檢視更多系列文章:系列文章目錄

相關文章