HashMap原始碼閱讀

2022-05-17 08:39:13 字數 4747 閱讀 3572

繼承樹

注意下方的"元素"二字

按照習慣,先看建構函式和第一次新增

首先無參的建構函式

讓過載因子等於0.75

public hashmap()
然後是put函式,呼叫了內部的putval函式

public v put(k key, v value)
在看這個方法之前,要先知道hashmap中儲存元素的型別

內部類node

首先,hashmap的鍵值對,儲存在內部類node中

它內部有四個屬性

final int hash;

final k key;

v value;

nodenext;

hash用來儲存hash值,next是處理hash衝突用的

方法有:

四個引數的構造器

key和value的getter

tostring

hashcode

setvalue

equals

**table

儲存資料的實際陣列就是node table

putval方法,通過注釋解釋

大致邏輯就是,如果占用的雜湊位址已經超過了負載容量,並且新插入元素的負載位址已經有元素了,那麼開啟擴容

當onlyifabsent為false時:

在對應的雜湊位址中尋找有沒有key相同的元素,如果有,相應的value取而代之,返回舊的value

如果沒有,將元素插入鍊錶末尾,如果鍊錶長度在插入後大於等於8,則將鍊錶變為樹型結構

當onlyifabsent為true時:

找到了key相同的元素就直接返回,不會替換

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;}}

//沒有找到key值是一樣的,標記進行了增刪操作

++modcount;

//如果已經利用的table中雜湊位址的數量超過了負載容量,則擴容

//也就是說,如果沒有找到

if (++size > threshold)

resize();

//linkedhashmap留用

afternodeinsertion(evict);

return null;

}

讓我們在**一下putval呼叫的方法

resize()

主要邏輯就是先計算出新的容量(*2)和新的負載容量,然後將原來的元素重新插入到雜湊表中

final node resize() 

else if ((newcap = oldcap << 1) < maximum_capacity &&

oldcap >= default_initial_capacity)

//新的負載容量變為2倍

newthr = oldthr << 1; // double threshold

}//如果原來的負載容量大於0

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;

if (oldtab != null)

else

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

//把low和high放入相應的雜湊位置中去

if (lotail != null)

if (hitail != null) }}

}}return newtab;

}

final void treeifybin(node tab, int hash) 

tl = p;

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

//真正地構建一顆紅黑樹

//這裡先挖個坑,以後再來看吧

if ((tab[index] = hd) != null)

hd.treeify(tab);}}

雖然我沒有確切地分析treeify方法,但是還是說一下內部大致邏輯:

1.如果樹裡面還什麼都沒有,那就將插入的點作為root

2.把這顆樹當做普通搜尋二叉樹,找到新節點應該插入的位置,並將其插入

3.進行必要的平衡操作:balanceinsertion()方法

在比較時,將hash值作為key和判斷大小的依據

當hash值相等時,用到了comparableclassfor()comparecomparables()方法來操作

第乙個方法是返回能夠使用comparable介面來判斷大小,如果能,則使用第二個方法,如果不能或者判斷結果仍然是相等,則使用tiebreakorder()方法進行判斷

這就是乙個基本的插入流程了.

讓我們回頭看一下一些注意點

構造器

無參構造器,負載因子指定為預設值

public hashmap()
public hashmap(int initialcapacity, float loadfactor)
指定容量,負載因子指定為預設值

public hashmap(int initialcapacity)
讓我們來分析一下這個方法

//這是在幹啥qaq

//實際上就是把從最高位的1開始

//將低位全部置為1

//讓後加1返回.

//將cap寫成二進位制數,手動模擬一下就可以看得很清楚了

//因為1+2+4+8+16 = 31

//所以剛好處理int正數

static final int tablesizefor(int cap)

key與value集合hashmap提供了keyset()方法,返回乙個set包含了所有key值

觀察這個方法可以發現,如果hashmap的成員變數keyset不為空,那麼直接放回,否則生成乙個keyset

但是這個類何時更新,怎麼賦值,我都沒找到原始碼,說是詭異一點都不過分

values()方法也一樣,hashmap還提供了entryset()方法,都是大同小異的

hash

眾所周知,hash是將乙個物件對映為乙個正整數,物件千變萬化而hash值範圍有限,所以可能出現多個物件對應乙個正整數的情況

在object中,有public native int hashcode();這樣乙個方法,它的實現我們不知道是什麼,但是可以猜測,因為定義在object內,應該與物件的屬性無關,而與型別,位址等可能有關.

string等類重寫了hashcode方法,如果自定義類也想放入hashmap也需要重寫hashcode方法,當然還有equals方法.但是由於這兩方法都定義在object中,所以不寫也不會報錯

hashmap中,不僅僅是簡單地呼叫了物件的hashcode方法

比如

static final int hash(object key)
這個方法在public v put(k key, v value)中被呼叫了

它實際上是將key的hashval處理了一下

但是為什麼要這麼處理呢?

這是一種hash擾動,如果只有高位不等,低位相等時,hash&(length-1),當length<2^16時就會相等,為了避免這種情況,於是高位與低位異或,使得低位與高位相關聯.

那麼能讓本來低位一樣的兩個hash值變得低位不一樣,當然也是有可能讓本來兩個低位不等的hash值變得低位相等的

但是hashcode這麼寫仍然很有必要,因為key可能是自定義的,你自己重寫的hashcode方法可能有失水準

多嘴一句,在hashmap中,對陣列長度取模,用的是&運算哦

HashMap原始碼閱讀

the default initial capacity must be a power of two.預設容量必須是2的n次方,預設大小為16 static final int default initial capacity 16 the maximum capacity,used if a h...

原始碼閱讀 HashMap

資料結構 jdk1.8對hashmap進行了比較大的優化,底層由以前的陣列 鍊錶變成了陣列 鍊錶 紅黑樹的實現形式,當鏈結結點較少時用鍊錶,當鏈結結點超過一定值的時候用紅黑樹。繼承實現 屬性 預設容量 static final int default initial capacity 16 最大容量...

HashMap 1 8 原始碼閱讀

一 初始化 1.無參建構函式 負載因子預設值 static final float default load factor 0.75f 指定loadfactor負載因子的值是0.75f public hashmap 2.指定初始化大小和負載因子 hashmap的最大容量 static final i...