static final int default_initial_capacity = 1 << 4;
static final int maximum_capacity = 1 << 30;
static final float default_load_factor = 0.75f;
當hashmap的容量增加到當前容量的3/4時,會觸發擴容
static final int treeify_threshold = 8;
static final int min_treeify_capacity = 64;
當hashmap的某個槽位的鍊錶節點數量達到7時,同時底層陣列的長度達到64時,會將鍊錶轉為紅黑樹,如果底層陣列長度不足64,將會觸發擴容
static final int untreeify_threshold = 6;
如果某個槽位的資料元素個數降低到6個時,並且這個槽位是紅黑樹結構,那麼就會退化為鍊錶結構,退化操作在resize擴容時才會觸發
static final int hash(object key)
key的hash值決定資料儲存位置。
transient node table;
通過key的hash計算得到在陣列中的位置,然後將資料儲存在對應位置的鍊錶或者是紅黑樹下
transient set> entryset;
通過實體去遍歷map中的元素時,返回的就是這個物件。
public hashmap(int initialcapacity, float loadfactor)
public hashmap(int initialcapacity)
public hashmap()
static final int tablesizefor(int cap)
可以指定初始容量,以及載入因子,但一般我們都會用預設值,對於初始容量,一般都是2的n次方,如果你傳入的不是2的冪次方,tablesizefor方法會將該數值轉為2的冪次方,如:初始化的容量為11,tablesizefor方法會自動計算出16。
public hashmap(map<? extends k, ? extends v> m)
final void putmapentries(map<? extends k, ? extends v> m, boolean evict)
else if (s > threshold)
resize();
//迴圈從引數map中獲取資料插入新的map中
for (map.entry<? extends k, ? extends v> e : m.entryset())
}}
先對底層陣列進行初始化或者擴容,然後對傳入的map進行迴圈put操作。
public v put(k key, v value)
final v putval(int hash, k key, v value, boolean onlyifabsent,
boolean evict)
// 如果說元素重複的話就不新增了
if (e.hash == hash &&
((k = e.key) == key || (key != null && key.equals(k))))
break;
// 一直訪問鍊錶的下乙個元素
p = e;}}
//如果這是個覆蓋key的操作而不是插入操作,就返回舊的值
v oldvalue = e.value;
if (!onlyifabsent || oldvalue == null)
e.value = value;
afternodeaccess(e);
return oldvalue;
}} ++modcount;
if (++size > threshold)
resize();
afternodeinsertion(evict);
return null;
}
1.因為hashmap的底層有陣列結構,所以首先要完成陣列的初始化操作。而陣列的初始化操作一般在resize這個擴容操作中完成。
2.當我們在執行putval操作時,首先通過hash演算法得到陣列存在在陣列中的位置,如果這個位置為空,就建立乙個node節點,將資料放入陣列,如果陣列的位置元素並不為空,那麼進行往鍊錶或者紅黑樹中新增元素,或者正好鍊錶轉紅黑樹的操作。
3.當我們向陣列的指定位置追加元素時,首先宣告了乙個臨時節點e,第乙個判斷表示引數傳入的key所定位到的位置有資料,並且還有相同的key,這時候執行的是hashmap的put覆蓋操作。
4.如果說put操作定位的位置返回的是紅黑樹的節點,那麼就向紅黑樹新增新的元素
5.第三中情況就是我們向鍊錶中追加元素,而這個鍊錶是個單向的鍊錶。
6.當新加入的元素使煉表達到7個元素時,這個鍊錶就要轉為紅黑樹,但是如果在鍊錶中找到了重複的key,此時就直接跳出迴圈,因為我們只是普通的put操作,所以在跳出迴圈後onlyifabsent=false,會將value進行覆蓋操作,但是如果onlyifabsent=true,表示map中已存在key,就不會更新資料
7.當然,如果是新增不同的元素,還是要對集合的大小加1並且判斷是否需要擴容,這裡可以看出擴容是在加入元素之後就執行的操作。
8.當觸發鍊錶轉紅黑樹的操作時,並不一定會轉為紅黑樹,還有乙個條件就是底層陣列的長度要大於64,否則此時觸發的是擴容,而不是轉為紅黑樹。
public v get(object key)
final nodegetnode(int hash, object key) while ((e = e.next) != null);}}
return null;
}
引數key要進行hash運算,獲取hash值定位元素在底層陣列的位置,如果底層陣列為空,或者陣列長度為0或者指定位置的元素為空,這些都將返回null物件,
否則如果鍊錶或者紅黑樹的第乙個元素的hash值就是我們要找的資料就直接返回,否則就判斷返回的節點是鍊錶節點還是紅黑樹節點,如果是紅黑樹,就通過紅黑樹的方法去查,否則就遍歷鍊錶
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
newcap = oldthr;
else
//如果新的閾值等於0,表示這個是乙個空表,並且舊的閾值大於0
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;
}
在進行擴容之前,先獲得底層陣列的長度,此處底層可能還是個空陣列,對於向空陣列中新增元素也要先擴容,再新增元素
處理擴容有多種情況,第一,底層並不是空陣列,此時如果陣列的長度已經大於等於支援的最大容量了將不會執行擴容操作。
如果擴容後的容量超過了hashmap支援的最大容量,會設定乙個新的閾值,這個閾值是容量值乘以載入因子。然後將之前hashmap底層的陣列複製到新的陣列下,如果陣列元素不是鍊錶,就直接賦值,如果槽的節點是紅黑樹,就進行紅黑樹的處理,
如果槽點是鍊錶,需要進行額外處理,而並不是把這個鍊錶全部直接移動到另乙個陣列的槽位上,而是要對鍊錶中的資料進行迴圈遍歷,然後將鍊錶資料分為兩個鍊錶,乙個鍊錶的所以位置不變,另乙個鍊錶的索引位置是j + oldcap,並且其他槽位的資料在拆分為兩個鍊錶時也不會與之前的衝突
第一次寫技術部落格,寫的不好請大家見諒,有錯誤之處還請大家指正。語言比較接地氣,我覺得還是比較容易理解的。
JDK1 8 HashMap原始碼解析
普通常量 儲存node鍊錶的陣列 transient node table 由node節點構成的set集合 transient set entryset hashmap儲存元素的數量 transient int size 記錄hashmap結構性變化的次數 value覆蓋不算 和fail fast機...
JDK 1 8 HashMap原始碼解析
put方法分析 public v put k key,v value hash方法解析 減少hash衝突 static final int hash object key putval方法具體實現 final v putval int hash,k key,v value,boolean onlyi...
JDK 1 8 HashMap擴容原理
擴容前計算索引 1010 0101 0000 1111 0000 0101 索引結果 5擴容以後容量是n 32 對應的二進位制是0001 1111 node本身的hash值是不變的,仍然是1010 0101,那麼擴容後node 的索引的計算是通過如下方式得到 擴容後計算索引 1010 0101 00...