仔細瞄一下HashMap是怎麼幹活的

2022-07-15 01:45:07 字數 2712 閱讀 6177

以下分析基於jdk11.0.2

先畫一張圖

hashmap(),hashmap(int initialcapacity),hashmap(int initialcapacity, float loadfactor)。這三個方法都直接或間接地會初始化loadfactor(載入因子)和threshold(擴容閾值)。其中threshold=capacity*loadfactor。

當呼叫hashmap()建立hashmap時,threshold的值會在第一次resize()時賦值。由default_load_factor * default_initial_capacity可知threshold=0.75*16=12

當呼叫hashmap(int initialcapacity)/hashmap(int initialcapacity, float loadfactor) 建立hashmap時,threshold由 loadfactor*tablesizefor(int cap) 計算得出。

int h; return (key == null) ? 0 : (h = key.hashcode()) ^ (h >>> 16);

該方法首先呼叫了hash()方法獲取key對應的hash值,然後呼叫putval(int hash, k key, v value, boolean onlyifabsent, boolean evict)…

該方法將key的hashcode的高16位與低16位進行了一次異或位運算(hashcode為32bit的int型別,雖然值只有31bit)。v1.8+中該方法的實現較之前版本更容易發生hash碰撞(之前版本為4次異或運算),這是權衡效能和紅黑樹的優化…

該方法除了供put()呼叫,也提供給putifabsent()呼叫。在此暫時討論put()呼叫的情況,即 boolean onlyifabsent=false; boolean evict=true;

下面列出用無參建構函式new hashmap()建立的物件進行put的幾種情況:

2.2.1. 第一次put時,執行步驟如下:

1. 執行resize(),將map中的table初始化為大小為default_initial_capacity的node陣列;threshold賦值為default_load_factor * default_initial_capacity。

2. 使用hash, key, value建立node節點,作為鍊錶的頭節點存於table[i]中,下標為  i = (n - 1) & hash 。

2.2.2. 當put後table內節點數<=threshold(預設threshold=12,而此時table.size也就是capacity應為16,這兩個值會隨著resize更新)時,執行步驟如下:

1. 找到hash對應table中的鍊錶/樹

2. 當table存的是鍊錶時,把key-value存入鍊錶尾節點或替換key對應節點的value值,並判斷鍊錶長度是否》treeify_threshold(預設值8),如果是則呼叫treeifybin()。呼叫treeifybin()時會判斷是否需要將該鍊錶轉為樹。

而在treeifybin()方法中,只有當table.size>=min_treeify_capacity(預設值64)會轉為樹,否則只是resize()擴容;而當table存的是樹時,呼叫treenode.puttreeval()在樹中存入/替換資料。

2.2.3. 當put後table內節點數》threshold時:

執行完2.2.2的操作後,執行執行resize():capacity翻倍(<<1),threshold也重新計算。

畫了張流程圖用來精簡表示putval:

在putval途中呼叫有兩種情況下hashmap會呼叫resize()進行擴容和table資料遷移(遷移機率50%):

3.1.1. 未指定initialcapacity或loadfactor值:

建立table,大小為default_initial_capacity(預設值16);賦值threshold=default_load_factor * default_initial_capacity(預設值12)。

3.1.2. 已指定initialcapacity或loadfactor值:

建立容量為tablesizefor(initialcapacity)的table;給擴容閾值賦值 threshold = loadfactor * tablesizefor(initialcapacity)。

簡單說明一下tablesizefor(int cap)函式:返回值為大於等於cap且與cap差值最小的2^n的值。例如3->4,4->4,9->16,65->128。

3.3.1. 重新計算table容量capacity和擴容閾值threshold,值皆為原值的2倍(<<1),建立新table[capacity]

3.3.2. 遍歷原table中的鍊錶/樹,

當鍊表為單節點時:將該節點放至新table,下標為hash&(capacity-1) ;

當鍊表為多節點時:遍歷該鍊錶並分離出一條需要移動位置的鍊錶,將2條煉表放至新table。可根據hash&oldcapacity==0判斷node是否需要移動;

當鍊表為紅黑樹時:呼叫treenode.split()將樹拆分/移動。當樹的大小<=untreeify_threshold(預設6)時會退化成煉表。

是時候,反思一下

時光荏苒,一晃,我已過而立之年,人生之路已經走完小一半了,回頭想想在上海工作的這幾年,我的心理狀態發生了很大的變化,從嚮往上海,到想逃離上海 從任性,依賴父母,到成熟,為父母和家庭分憂。然而,在上海待久了,安家的意願越來越強烈,歸屬感越來越稀薄,對未來越來越迷茫,是時候,給自己的人生做乙個反思了,出...

仔細研究一下方法呼叫和屬性訪問

仔細研究一下方法呼叫和屬性訪問。通過為乙個類新增方法 invokemethod 可以有機會執行原本未定義的方法,示例如下。class myclass def invokemethod string name,object args def mine new myclass assert mine.h...

是時候思考一下了

我也不喜歡分類,所以姑且隨便寫寫吧,這個暑假沒有立即回家,在學校跟老師做專案 算了,還是說自己在做吧,老師是太強了 數字影象處理,但是感覺很難,一直進展並不順利,而且隊友少,且沒有合作精神,當然我也沒有。感覺只是非常欠缺,但是又沒有老師指導,所以每前進一步都很難。總之很不順利,不過最近看見乙個仁兄的...