HashMap原始碼分析(重要)

2021-09-19 12:07:04 字數 3983 閱讀 5254

default_initial_capacity = 1 << 4; // aka 16(桶的個數)

default_load_factor = 0.75f(負載因子)

treeify_threshold = 8;(樹化閾值)

min_treeify_capacity = 64(樹化要求的最少雜湊表元素數量)

untreeify_threshold = 6;(解除樹化閾值,resize階段)

樹化總結:當桶中鍊錶元素個數超過8,並且雜湊表中所有元素個數超過64,此時會將桶中鍊錶轉為紅黑樹結構。否則(只是鍊錶元素個數超過8),只是簡單地進行擴容操作而已。樹化針對符合條件的桶,只有桶中鍊錶的元素超過8,並且雜湊表中元素個數超過64,該雜湊桶才會樹化,並不是所有的桶都樹化。

樹化的好處

1)便於查詢  鍊錶:t(n)=o(n)   二叉樹t(n)=o(log(n))

2)安全問題  黑客服務:雜湊碰撞拒絕服務。一直呼叫hashmap的put(),以前的hashmap會一直在桶後面+++,就會造成大量的

雜湊都碰撞在同乙個位置上,導致cpu佔用率達到100%,整個伺服器掛掉。面試回答查詢快即可,不用答雜湊碰撞(避免面試官誤以為懂網路安全)。

為什麼要樹化?

當桶中鍊錶長度太長會大大影響查詢速度,因此將其樹化來提高指定節點的速度。

為什麼要使用紅黑樹?結合紅黑樹的特點回答,紅黑樹是乙個根結點為黑色,每個紅色結點的孩子結點也是黑色,從任一結點到其葉子結點的所有路徑都包含相同數目的黑色結點的平衡二叉樹。

hashmap採用lazy-load策略(當第一次使用put()時,才會將雜湊表初始化。)

//無參構造

public hashmap()

//有參構造一

public hashmap(int initialcapacity)

//有參構造二

public hashmap(int initialcapacity, float loadfactor)

注意:要求初始化容量必須為2的n次方,若通過構造方法傳入乙個非2^n數值,hashmap會在內部呼叫tablesizefor返回乙個

距離最近的2^n數值。eg:傳15,返回16;傳31,返32;傳100,返回128.

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

boolean evict)

//鍊錶中存在相同key,替換其value。

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;

afternodeaccess(e);

return oldvalue;}}

++modcount;

//新增節點後,整個hashmap的元素個數若要超過容量時,呼叫resize()進行擴容工作。

if (++size > threshold)

resize();

afternodeinsertion(evict);

return null;

}

put方法流程:

1.若hashmap未初始化,呼叫resize()進行初始化操作。

2.對key值hash取得要儲存的桶下標

1)若桶中為空,將節點直接作為桶的頭結點儲存

2)若桶中不為空

a.若樹化,使用樹的方式新增新節點。

b.將新節點以鍊錶形式尾插到最後。

-新增元素後,鍊錶的個數bincount>=樹化閾值-1,嘗試進行樹化操作。

3)若桶中存在相同key節點,替換value值。

3.新增元素後計算整個雜湊表大小,若超過threshold(容量*負載因子),進行resize()擴容操作。

結論:16*0.75=12,故所有桶的數量超過12,桶就擴容了。

final nodegetnode(int hash, object key)  while ((e = e.next) != null);}}

return null;

}

get方法流程:

1.若雜湊表已經初始化,並且桶的首節點不為空。

1)查詢節點的key值恰好等於首節點,直接返回首節點。

2)進行桶元素的遍歷,查詢指定節點

a.若樹化,按照樹的方式查詢。

b.按照鍊錶方式查詢。

2.雜湊表為空或桶的首節點為null,直接返回null。

final node resize() 

//將雜湊表double擴容

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

//if (newthr == 0)

threshold = newthr;

@suppresswarnings()

node newtab = (node)new node[newcap];

//將雜湊表指向擴容後的新錶

table = newtab;

//將原表中的元素移動到新錶

//策略:原來的元素要麼待在原桶中,要麼移動到double size的桶下。

if (oldtab != null)

else

} while ((e = next) != null);

if (lotail != null)

if (hitail != null) }}

}}

return newtab;

}

擴容resize流程:

1.判斷雜湊表是否初始化,若還未初始化,根據initcapcity值進行初始化操作

2.若表已初始化,將原雜湊表按照 2倍方式擴容

3.擴容後進行原表元素的移動

1)若桶中元素節點已經樹化,呼叫樹的方式移動元素

(若在移動過程中發現紅黑樹節點<=6,會將紅黑樹解除樹化,還原為鍊錶)

2)若未樹化,呼叫鍊錶的方式來移動元素。

1.多執行緒場景下,由於條件競爭,很容易造成死鎖。(使用concurrenthashmap代替)

2.rehash是乙個比較耗時的過程。(在能預估儲存元素個數的前提下,盡量自定義初始化容量,儘量減少resize過程)。負載因子可以控制resize頻率:負載因子較小,擴容頻繁;負載因子較大,增加碰撞機率。因此負載因子盡量不要改。

//返回高低16位共同參與運算的雜湊值

static final int hash(object key)

///h>>>16:將h無符號右移16位,實際上,就是將高16位移到低16位,然後和原來的所有數進行異或運算。

問題:為何不直接採用object類的hashcode()返回桶下標:因為object類的hashcode()幾乎不會發生碰撞,需要的桶個數太多。

為什麼容量必須為2的n次方?

用位運算替代數學取模運算,提高速度。

hashmap允許key和value為空,如果傳進來的key為空,則放入第乙個桶中,否則計算它的雜湊碼。

HashMap原始碼分析

public hashmap int initialcapacity,float loadfactor 2 接下來是重要的put方法,put方法用於將鍵值對儲存到map中,讓我們來具體分析一下。public v put k key,v value if key null 若key為null,則將va...

HashMap 原始碼分析

1 getentry object key 方法 final entrygetentry object key return null 根據key的hash值計算出索引,得到table中的位置,然後遍歷table處的鍊錶 for entrye table indexfor hash,table.le...

HashMap原始碼分析

public v put k key,v value if key null return putfornullkey value int hash hash key int i indexfor hash,table.length for entrye table i e null e e.nex...