JDK1 8原始碼 HashMap解讀

2021-08-31 11:56:26 字數 4083 閱讀 1455

hashmap是我們最常用的集合型別之一了。也由於其高效、使用方便,我們有必要對其內部進行梳理一番。

jdk1.8原始碼中,關於map的類圖關係如下:

map的類圖關係中,我們可以看出還是蠻豐富的。需要用到順序的,可以使用treemap,需要執行緒安全的可以使用hashtableconcurrenthashmap。其各自特點總結如下:類特點

hashmap儲存資料無序;非執行緒安全;key可為,但是其桶索引為0;效率為o(1)

hashtable遠古時代就具有的,儲存資料無序;執行緒安全,但是是使用笨重的synchronized;key不可為

concurrenthashmap新貴,儲存資料無序;執行緒安全,採用cas;key不可為

treemap儲存資料有序;非執行緒安全

linkedhashmap儲存資料有序;非執行緒安全

讓我們先從hashmap開始。

jdk1.7中,hashmap由陣列和鍊錶構成,當鍊表資料特別多的時候,很明顯的其效率受到影響。於是,在jdk1.8中的hashmap當鍊表資料過長時,轉為紅黑樹的資料結構。

我們選取常用的幾個方法(例項化、put)來看下原始碼。

//map預設初始化的容量

static final int default_initial_capacity = 1 << 4; // aka 16

//map的最大容量

static final int maximum_capacity = 1 << 30;

//預設負載因子

static final float default_load_factor = 0.75f;

//鍊錶轉為紅黑樹的閾值

static final int treeify_threshold = 8;

//紅黑樹轉為鍊錶的閾值

static final int untreeify_threshold = 6;

//鍊錶轉為紅黑樹表的閾值

static final int min_treeify_capacity = 64;

//桶陣列

transient node table;

//對map的entryset結果快取

transient set> entryset;

//key-value對的數量

transient int size;

//增加或者刪除map元素、rehash的次數

transient int modcount;

//hashmap需要resize的閾值

int threshold;

//負載因子

final float loadfactor;

選取個複雜點的構造方法:

//initialcapacity表示hashmap的容量,loadfactor表示負載因子

public hashmap(int initialcapacity, float loadfactor)

//tablesizefor的方法如下

//目前演算法我看的不太明白

static final int tablesizefor(int cap)

大家通過源**可以看到,初始化時,可以說幾乎沒發生沒什麼事情。只賦值了loadfactor和確保map的容量為2的冪次方。

這裡就有個問題?為什麼需要確保map的容量為2的冪次方?其實這是個非常規的設計,常規的設計是把桶的大小設計為素數(參考:在講完put方法後我們來闡述。

其實,這裡可以看作是map的延遲初始化。在首次put元素時,會初始化屬性table。在這裡順便提下table的型別node。在為鍊錶時,其資料結構為:

//鍊錶節點

static class nodeimplements map.entry

當轉為紅黑樹時,其結構為:

//樹節點

static final class treenodeextends linkedhashmap.entry

final v putval(int hash, k key, v value, boolean onlyifabsent,

boolean evict)

下乙個節點不為空,判斷key是否相同,相同則覆蓋舊值

if (e.hash == hash &&

((k = e.key) == key || (key != null && key.equals(k))))

break;

p = e;}}

v oldvalue = e.value;

if (!onlyifabsent || oldvalue == null)

e.value = value;

//為linkedhashmap預備

afternodeaccess(e);

return oldvalue;}}

//新增修改次數

++modcount;

//超過閾值則分配空間,重新rehash運算

if (++size > threshold)

resize();

//為linkedhashmap預備

afternodeinsertion(evict);

return null;

}

我們可以畫個流程圖:

其他的方法,譬如getremove等,主要的都是先要確定key對應的索引。即hash & (n - 1)。其中hashkey對應的hash值,ncapacity,即map的容量。

我們先來看下hash的計算:

static final int hash(object key)
為什麼hashcode需要與其高16為抑或?簡單點說就是讓高位和低位都參與運算,使key的分布盡量更均勻些。參見

有了hash值之後,我們可以計算索引的位置了。一般都是取模運算,但是大家都知道計算機是二進位制的,位運算會比取余快多了。所以這裡的hash & (n - 1)可以看做是用空間來換取時間。因為當n為2的冪次方時,n-1的二進位制恰恰全是1,其與hashcode的二進位制與值正好是取模的結果。這裡就回答了上面為什麼需要確保map的容量為2的冪次方的問題。

hashmap的原始碼目前就分析到這裡。流程其實說起來不是很難,難的關鍵就是為什麼設計者會是這樣的設計?這恰恰是我們需要多思考的。本篇在關於這一點上還是遠遠不足的,大家可以多搜尋幾篇來看看,多思考思考。

HashMap原始碼分析 JDK1 8

陣列 鍊錶 紅黑樹 陣列儲存元素,當有衝突時,就在該陣列位置形成乙個鍊錶,儲存衝突的元素,當鍊表元素個數大於閾值時,鍊錶就轉換為紅黑樹。transient node table transient int size int threshold final float loadfactor 預設初始容...

HashMap原始碼分析 (JDK1 8

首先,hashmap儲存結構類似於位桶,總體結構是 位桶 鍊錶 紅黑樹,這與之前版本的實現有所改進。常量域預設容量16 static final int default initial capacity 1 4 最大容量2的30次方 static final int maximum capacity...

HashMap原始碼分析jdk1 8

hashmap作為一種最常用的集合型別之一,他的實現是用的雜湊表,在這就不進行雜湊表詳細的解釋。為解決雜湊表的衝突問題,hashmap即是採用了鏈位址法,也就是陣列 鍊錶的方式。廢話不多說,我們還是通過原始碼來進行hashmap的詳解。組成hashmap的基本單元node節點 node節點的定義 s...