在jdk1.8裡邊 hashmap的實現原理不僅僅只由位桶+鍊錶實現 ,增加位桶+紅黑樹實現,在桶中鍊錶儲存的元素個數到達閾值時(預設為8),將在底層將鍊錶轉為紅黑樹,但因為我本人紅黑樹掌握的並不好,所以不討論有關紅黑樹,本文主要講解基礎,hashmap的put方法,其他請等待下文;
一,所涉及到的名詞;
1,bucket 桶,對於系統開始初始化hashmap的時候,會建立乙個長度為capacity的陣列,陣列裡邊儲存元素的位置被稱為桶,儲存乙個node
2,capacity 容量 capacity就是指hashmap中桶的數量,預設初始值為16,必須為2的冪
3,size 表示hashmap中存在kv的數量(陣列和鍊錶的總和),如果size到達threshold極限值會進行擴充。
4,loadfactor 負載因子(裝載因子) ,裝載因子用來平衡hashmap滿的程度,預設值為0.75f.計算hashmap的實時裝載因子的方法為:size/capacity,而不是用已占用桶的數量去初以capacity 滿的程度就是當前的桶中被占用了多少。 例如 現在capacity=16,占用了乙個桶size = 1, 1/16 = 0.0625 就是表示現在有多滿。
5,threshold 極限值 size的值到達極限值之後就會進行擴充。
threshold=capacity*loadfactor 例如現在capacity=16, loadfactor = 0.75 16*0.75 = 12 意思就是當現在桶和鍊錶中的bin總量等於12 時,就會擴充
二,原始碼分析:
1.初始化:
hashmap 繼承於abstractmap,實現了map,cloneable,serializable介面
hashmap是無序的,有序的有treemap,linkedhashmap等
靜態成員定義:
static final int default_initial_capacity = 1<<4; //初始值為16,並且必須是2的冪
static final int maximum_capacity = 1<<30; //最大容量 2^30
static final float defalt_load_factor = 0.75f; //負載因子
static final int treeify_threshold = 8; //由鍊錶轉為數的極限值
static final int untreeify_threshold = 6; //由樹轉為鍊錶的閾值
static final int min_treeify_capacity = 64;
//當桶中的資料被樹化時最小的hash表容量(如果沒有達到這個閾值,即hash表容量小於min_treeify_capacity,當桶中bin的數量太多時會執行resize擴容操作)這個min_treeify_capacity的值至少是treeify_threshold的4倍。
桶中的每個資料是node的例項,其中包含node(int hash,k key,v value, nodenext); 也就是說桶中的元素所包含這些屬性
node 是 hashmap中的乙個內部靜態類,他的初始定義如下:
他的內部重寫了equals和hashcode方法
static class nodeimplements map.entry
public final k getkey()
public final v getvalue()
public final string tostring()
public final int hashcode()
public final v setvalue(v newvalue)
public final boolean equals(object o)
return false;}}
我們一般使用
hashmap
例項化使用無參的構造方法,
只是初始化和負載因子
還有其他三種種構造方法,分別為其指定桶的大小,桶的大小和負載因子,將乙個新的map填充進去
構造方法在下一章講解 一併講解tablesizefor方法
2.put
方法(重點)
hashmap中我們用的最多的就是
put,get
方法了。
在這之前我們先了解一下put方法所相關的兩個方法
hashmap在初始化的時候不會去初始化node table 只有在你往map中新增值的時候他才回去判斷table有沒有被初始化,沒有則去初始化,並設定預設大小;
初始化: 紅色 注釋是初始化過程要執行的
(1).有關桶的初始化和size的擴充()
final node resize()
//先讓新的容量等於舊容量二倍 之後新容量沒到達最大值, 並且之前容量大於等於初始化大小則將閾值擴大二倍
else if ((newcap = oldcap << 1) < maximum_capacity &&
oldcap >= default_initial_capacity)
newthr = oldthr << 1; // double threshold
} else if (oldthr > 0) // initial capacity was placed in threshold
//如果舊的閾值大於0, 則賦值給新的容量 在這裡操作的前提是在初始化hashmap時呼叫的是帶參的構造方法,
//設定過閾值,但是閾值實際是容量值 詳情請看在帶參初始化時呼叫tablesizefor方法
newcap = oldthr;
else
if (newthr == 0)
threshold = newthr;
@suppresswarnings()
//申請新大小的桶陣列
node newtab = (node)new node[newcap];
//並且將新的空間賦值給原始桶陣列
table = newtab;
if (oldtab != null) else
} while ((e = next) != null);
if (lotail != null)
if (hitail != null)
} } }
} //返回新陣列
return newtab;
}
(2).hashmap中hash值獲取方式
static final int hash(object key)
// 我們在
put的時候,傳入的是
string
型別的鍵值,存放的就是
string
型別嗎?
我們一步一步深入解析
public v put(k key, v value)
這是put()方法的原始碼,主要操作在putval中,
put方法的執行過程大致是這樣的,取得鍵值,並取得到鍵的hash值,根據hash&(陣列長度-1)作為索引尋找桶的位置,然後檢測該索引下是否存在值,沒有,將node(前邊有相應的構造方法)型別的值加入引數已注入,若有,先判斷他們的hash值是否一致(不同的hash值&相同值可能存在 結果一致,即索引一致)並且鍵值是否一致,若都一致,則覆蓋對應的位置node裡邊的值,並將原來的值返回,若不相等,遍歷鍊錶,若一直沒有滿足要求的值,則將當前node儲存在當前位置的桶中,並且將指標指向原來鍊錶的首部。
//如果找到鍵值一樣的,停止鍊錶遍歷
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
//沒找完且沒找到,則繼續找,然當前引用指向下乙個引用(c 語言鍊錶的遍歷)
p = e;}}
//如果桶陣列或者鍊錶中存在和當前的node鍵相等的
v oldvalue = e.value;
//如果允許覆蓋值,或者之前的值為空
if (!onlyifabsent || oldvalue == null)
//覆蓋
e.value = value;
//供linkedhahmap繼承使用的
afternodeaccess(e);
//返回舊值
return oldvalue;}}
// modcount字面意思就是修改次數
++modcount;
//即不存在碰撞 將node放入桶表中
//如果當前桶陣列的size達到閾值,擴充套件
if (++size > threshold)
resize();
//供linkedhahmap繼承使用的
afternodeinsertion(evict);
return null;
}
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 ...