HashMap原理 原始碼解析

2021-08-26 19:12:55 字數 4101 閱讀 1369

一、前言

hashmap是map實現中最常使用的,具有快速訪問的優點,所以很有必要深入到原始碼去了解其實現原理。 方法

的原始碼分析。

二、hashmap的資料結構

hashmap

可以理解為由陣列鍊錶組成的儲存結構,如圖

軸方向上是乙個陣列,

y方向是鍊錶。乙個節點的資訊包括

hash

、key

、value

和next。

static class entryimplements map.entry
三、常用方法原始碼分析

1、hashmap的主要相關變數和建構函式

static final int default_initial_capacity = 16; 

static final int maximum_capacity = 1 << 30;

static final float default_load_factor = 0.75f;

transient entry table;

transient int size;

int threshold;

final float loadfactor;

default_inital_capacity:預設初始化容量,16

maximum_capacity :

最大容量,即2的

30次方

default_load_factor :

預設負載係數

entry table:

entry

型別的一維陣列,長度必須是

2的冪,也就是上圖的

table陣列

int size: 

當前hashmap

的元素個數

int threshold: 

擴容的臨界值,即

capacity*loadfactor

final float loadfactor:

hash

表的負載係數

public hashmap(int initialcapacity, float loadfactor)
hashmap

的初始化主要是

initialcapacity

和loadfactor

這兩個引數。前者表示上圖中

x軸方向

table

的長度,即不同

hash

的key

的長度,

後者是負載係數,擴充的臨界值

threshold=capacity*loadfactor

,當達到擴充臨界值時,

hashmap

會自動擴容。比如預設情況是

16*0.75=12

,當填充到

12時,

hashmap

會自動將

x軸方向的

table

的長度擴充到32。

hashmap

裡面的capacity

,長度是

2的冪,

即使你初始化時不是

2的冪,

它也會幫你轉為2的冪

。從上面的**可以看出來。

2、新增元素

public v put(k key, v value) 

}modcount++;

addentry(hash, key, value, i);

return null;

}void addentry(int hash, k key, v value, int bucketindex)

基本思路:通過

key拿到

hash

,然後通過

hash

找到table

的索引值,找到之後比較該索引值所在列(

y軸方向 )

有沒有相同的

key,有的話就替換掉原來的

vaule

,返回舊的

value

;如果沒有相同的

key,則把當前元素加進該列的前面,即鍊錶的表頭。當容量大於等於擴容的臨界值時則擴充為原來

table

長度的2倍。

注意:hashmap

允許key

的值為null

那它是怎麼根據

hash

值返回該

hash

在table

裡面的索引呢?你可能會說,很簡單啊,取模就可以了,沒錯,但取模效率是比較低的,來看看

hashmap

裡面是怎麼實現的。

static int indexfor(int h, int length)
length,table的長度,也就是hashmap裡面的capacity,長度是2的冪,&是與運算,例如length為16,則length-1的二進位制為01111,當h為17,即10001,01111&10001=00001,通過與運算取代了取模運算,效率大大提高,這也是為什麼table的長度為2的冪。

3、獲取元素

public v get(object key) 

return null;

}

基本思路:根據傳過來的

key獲取其

hash

,快速定位到

table

的索引,再遍歷該索引鍊錶,用

equals

方法找出

該元素的value。

注意:一般

table裡

的同個的索引值的元素不會太多,這關係到查詢效能的問題,當達到擴容的臨界值時會自動擴容,然後

rehash

,即重新分配所有的元素來盡可能達到雜湊均勻。但

rehash

是很耗效能的,所以如果確定

hashmap

的容量,最好一開始就初始化好。

4、刪除元素

public v remove(object key) 

final entryremoveentryforkey(object key)

prev = e;

e = next;

}return e;

}

其實刪除不難,定義三個指標,乙個指向當前元素,另外兩個分別指向當前元素的前乙個和後乙個元素,這主要是鍊錶的操作。

5、擴容的實現

當達到的臨界值時,系統會呼叫

resize

函式來實現擴容。

void resize(int newcapacity) 

entry newtable = new entry[newcapacity];

transfer(newtable);

table = newtable;

threshold = (int)(newcapacity * loadfactor);

}void transfer(entry newtable) while (e != null);}}

}

基本思路:新建乙個更大的陣列,然後遍歷舊陣列,修改指標,把乙個個的元素放到新陣列上,每次插入都是插到鍊錶的表頭,並把舊陣列設為

null

。不算太難,但演算法精緻,值得慢慢品味

四、小結

1、hashmap採用陣列和鍊錶組成的儲存結構,用陣列方式儲存hash、key、value和next指標構成的entry物件,對於衝突採用鍊錶的方式解決;

2、hashmap插入時是插入到鍊錶的表頭,可能要擴大陣列的容量,擴容時需要重新計算hash,並複製物件到新陣列中,即rehash;

3、hashmap是非執行緒安全的。

HashMap原始碼解析

以jdk1.8為例,hashmap是乙個用於儲存key value鍵值對的集合,每乙個鍵值對是乙個node jdk1.7叫做entry 後台是用乙個node陣列來存放資料,這個node陣列就是hashmap的主幹。這裡我們主要來分析hashmap的get和put方法。public v put k k...

hashMap 原始碼解析

這幾天跳槽 被人問得最多的問題就是基礎方面的知識.當時學習的時候有點囫圇吞棗.現在回頭把這些基本的集合類原始碼都仔細閱讀下 hashmap 用的是最頻繁的.所以問得也最多了.initcapacity 初始化的容量 loadfacotr 負載因子 主要用來計算threshold的值 threshold...

HashMap原始碼解析

預設字段 static final int default initial capacity 1 4 預設node的陣列長度 16 static final int maximum capacity 1 30 陣列的最大長度 2 30 static final float default load ...