HashMap淺析(一)

HashMap淺析(一)

​ 無論幾年的程序猿,面試必問的題,HashMap算一大山,這座山翻不過去,離自己夢想的公司想必是無緣的。

​ 很多人覺得會用HashMap不就行了嗎,爲什麼非要研究它的原理呢?

​ 摸一摸自己的大光頭,你真的會用嗎?如何用效率高?除了當普通的數據結構使用,其他地方可不可以用到呢?

​ 接下來我會大致的對HashMap進行一個簡短的分析。學習是一個循循漸進的過程,不要妄想一晚上學會什麼原理,教你的是騙子,但你不能當傻子。

​ 第一節是對於數組容量爲2的冪次方的解惑。後續會慢慢補充

初始容量

HashMap的初始容量默認爲 1<<4,即16

//The default initial capacity - MUST be a power of two.
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4;

位運算講解

我先給大家介紹一下代碼片段中的位運算符,方便不懂的小夥伴知道如何計算

首先敲出來1的二進制,養成良好的習慣,32位記得寫全


位移前: 0000 0000 0000 0000 0000 0000 0000 0001


代碼中的<<是左移運算符,即左移四位.

這裏需要注意,不是單指左移二進制中的尾部1


位移中:0000 | 0000 0000 0000 0000 0000 0000 0001


如上面所示:你可以把那個“|”當作一個水龍頭,左移四位,就是讓水龍頭把離它近的四位0排出去


位移中:| 0000 0000 0000 0000 0000 0000 0001


排出去四位0之後呢,平白無故的少了四位兄弟,那得補充上來0對吧(空位補零)


位移後:0000 0000 0000 0000 0000 0000 0001 0000

位移前:0000 0000 0000 0000 0000 0000 0000 0001

儘管看上去像只是1左移了四位,但是並不能這麼想,如果是:…0001 0011 0001呢。

上面只是說了基礎的算法,那如果我想快速運算呢,不想通過二進制呢?

x<<y 相當於
xy2 x*y^2
x>>y相當於
x/2y x/2^y

迴歸正題

注意代碼上方的註釋:默認初始容量-必須是2的冪次方值

那麼爲什麼一定要讓容量爲2的冪次方值呢,帶着這個問題繼續看下去。

首先HashMap的底層數據結構:數組+鏈表,當鏈表長度大於8時,轉變爲紅黑樹;這是常見的答案。

數組是什麼數組?鏈表是什麼鏈表?(皮一下很開心)

//這一行代碼應該都能瞭解吧
transient Node<K,V>[] table;

衆所周知啊,HashMap中的數組使用散列即常說的哈希算法確定角標,使用鏈地址法解決哈希衝突。

//jdk1.8之後的hash算法實現,1.7的indexFor已經去掉了,但是用法沒變。
static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

這裏着重解釋一下(h = key.hashCode()) ^ (h >>> 16)

是不是看着很懵?懵就對啦

下面舉個例子:比如計算key爲“程序猿”的值,hashcode值:30804443

在這裏插入圖片描述

代碼中“>>>”是無符號右移,即邏輯右移,按照我上面位運算的講解,這個時候是需要整體向右移動16位,

即圖中黑豎線的右邊數字全部捨棄掉。

在這裏插入圖片描述

然後老規矩,空出來的位置補0

在這裏插入圖片描述

java中的^運算是異或,相同爲0,不同爲1的運算

在這裏插入圖片描述

可以看到通過這樣的運算,高16位其實沒變化。

說的直白點,這麼多步的運算,其實就是爲了讓key的hashCode值進行一個自身的高16位與低16位的異或運算,可以有效減少hash碰撞。

到這裏其實只是算出來了HashMap中的hash值,接下來還有一個最常問到的代碼片段

(n - 1) & hash

n是數組長度,hash是上面hash()方法算出來的hash值。

這裏可以試着想一想,n是2的冪次方,也就是2,4,8,16等等等,他們的二進制有什麼特點?

在這裏插入圖片描述

額,看起來並不明顯是吧,接下來再看看n-1的呢

在這裏插入圖片描述

低位都是1,對吧,接下來結合起來看吧!

首先解釋下&運算是按位與,即0&0=0,0&1=0,1&0=0,1&1=1.

那麼想一想上面計算得出的hash值,兩個進行按位與計算會是什麼樣的呢?

以n=16爲例

在這裏插入圖片描述

看出來了嗎,最終結果只跟hash值的最後幾位有關係!!!

長度採取2的冪次方的原因

首先假設現在數組長度非2的冪次方,比如n=10,(n-1)=9,它的二進制爲:1001,

現在有一個key=“程序猿”,通過上述的hash()方法計算出來的二進制的最後四位:1101,

1001&1101=1001

1001即得到的index值,但是換個思路想想,在保證n-1值(1001)不變的情況下,是不是還有另外的值會得到1001?當然是有的:1011.

而且還存在某些index值永遠不會用到,比如0111永遠不會出現。

結論:如果在不規定n爲2的冪次方情況下,很容易出現數組下角標index重複(出現重複的概率提升)或者存在某些index永遠用不到的情況,所以在n爲2的冪次方時,只要保證hash()算法本身分佈均勻就足夠了。
還有一個重要原因:在n爲2的冪次方情況下,hash%n等價於(n-1)&hash,而且"&“運算要比”%"快得多。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章