系列文章目錄java
前面咱們討論了HashMap的結構, 接下來幾篇咱們從源碼角度來看HashMap的實現細節.算法
本篇咱們就來聊聊HashMap的hash算法segmentfault
本文的源碼基於 jdk8 版本.數組
上一篇文章咱們提到, 爲了利用數組索引進行快速查找, 咱們須要先將 key
值映射成數組下標. 由於數組的下標是有限的集合, 因此咱們能夠先經過hash算法將key
映射成整數, 再將整數映射成有限的數組下標:函數
Object -> int -> index
對於 Object -> int
部分, 使用的就是hash function, 而對於 int -> index
部分, 咱們能夠簡單的使用對數組大小取模來實現.性能
下面咱們就來看看HashMap使用了什麼hash算法.code
首先咱們來看維基百科對於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(三): 關鍵源碼逐行分析之構造函數
查看更多系列文章:系列文章目錄