基于jdk1.8的HashMap源碼分析(一)

1.概念

? ? HashMap是Java中最常用的集合類之一轨奄,以<K,V>的形式來進(jìn)行數(shù)據(jù)存儲挽铁。常用來比較的還有HashTable袋倔,ConcurrentHashMap 等坦辟。

2.數(shù)據(jù)結(jié)構(gòu)? ?

? ? ? ? 什么是數(shù)據(jù)結(jié)構(gòu)鳄橘,在我的認(rèn)識中声离,我們的數(shù)據(jù)需要存儲在計算機(jī)中,那么它的存儲形式瘫怜,就是一個個的數(shù)據(jù)結(jié)構(gòu)抵恋。常見的數(shù)據(jù)結(jié)構(gòu)包括:數(shù)組,鏈表宝磨,二叉樹弧关,紅黑樹等等

? ? ? ? 提起hashMap內(nèi)部的數(shù)據(jù)結(jié)構(gòu) 那么大多數(shù)開發(fā)者都可以說出 在其內(nèi)部是維護(hù)了一個數(shù)組,數(shù)組的每個元素又是一個單向鏈表唤锉。在1.7之前的版本中世囊,這是沒錯的,但是維護(hù)單向鏈表由此帶來的一個缺點就是當(dāng)我們的hashMap發(fā)生大量的指針碰撞之后窿祥,當(dāng)我們需要get其中的一個值的時候株憾,就有可能需要遍歷單向鏈表,那么時間復(fù)雜度就是O(n),為解決這一問題,在1.7之后嗤瞎,hashMap中引入了紅黑樹墙歪,將達(dá)到一定條件的鏈表轉(zhuǎn)變?yōu)橐粋€紅黑樹,此時時間復(fù)雜度就變?yōu)榱薕(logn)贝奇。

問虹菲,為何不使用比如線性探測法 往數(shù)組的其他位置放 如果長度不夠 則進(jìn)行擴(kuò)容

答:第一,這樣有可能導(dǎo)致 本來數(shù)組還有空余位置就觸發(fā)了擴(kuò)容掉瞳,內(nèi)存浪費(fèi)毕源。第二,擴(kuò)容時 需要根據(jù)hash值和數(shù)組長度重新計算每一個key的hash值陕习,然后重新排列整個map霎褐,消耗系統(tǒng)資源「昧停總體上來說冻璃,是一個時間換空間的操作。


3.hashMap如何存儲數(shù)據(jù)

? ? 接下來就是本文的重點了损合,hashMap到底是如何存儲數(shù)據(jù)的呢省艳?

? ? 讓我們帶著幾個問題來看代碼

? ? (1)數(shù)組是什么時候進(jìn)行初始化的

? ? (2)數(shù)組在內(nèi)存中為一塊連續(xù)的內(nèi)存地址,所以要求我們在初始化數(shù)組是設(shè)定數(shù)組的長度塌忽。那么數(shù)組的大小設(shè)置為多少呢

? ? (3)hash值如何計算

? ? (4)什么是hash沖突拍埠,發(fā)生后沖突后hashMap是如何處理的

public V put(K key,V value) {

return putVal(hash(key), key, value,false,true);

}

這就是在hashMap中進(jìn)行put的方法失驶,我們需要關(guān)注兩個點 第一土居,putVal(hash(key), key, value,false,true) 這是進(jìn)行存儲操作的方法;第二嬉探,hash(key)這是計算hash值的方法 擦耀。

我們先來看第一個

transient Node[]table;

Node[] tab; Node p;int n, I;? ? ?

//聲明了一個Node對象,Node元素中包含了如下變量

final int hash;

final K key;

V value;

Nodenext;? //下一個元素的地址引用 佐證了這是一個單向鏈表

if ((tab =table) ==null || (n = tab.length) ==0)? ? //此處為判斷聲明的Node[]是否為空

n = (tab = resize()).length;? ? //為空執(zhí)行resize() 方法

在resize()方法中 涩堤,當(dāng)判斷oldTab.length==0時 有如下代碼

newCap=DEFAULT_INITIAL_CAPACITY;

newThr = (int)(DEFAULT_LOAD_FACTOR *DEFAULT_INITIAL_CAPACITY);

//下面兩行是類中聲明的常量

static final int DEFAULT_INITIAL_CAPACITY =1 <<4;// aka 16? 位運(yùn)算 即初始化的數(shù)組長度

static final float DEFAULT_LOAD_FACTOR =0.75f;// 負(fù)載因子

threshold = newThr;

@SuppressWarnings({"rawtypes","unchecked"})

Node[] newTab = (Node[])new Node[newCap];

table = newTab;

我們可以看到眷蜓,當(dāng)首次進(jìn)行put時,進(jìn)行了數(shù)組的初始化胎围,并定義了數(shù)組的長度為16吁系,那么問題來了,數(shù)組的長度給定了之后白魂,tab[]下標(biāo)為0-15汽纤。如果我們不斷插入數(shù)據(jù),當(dāng)key的hash值不在這16個長度之內(nèi)時會發(fā)生什么呢福荸,讓我們繼續(xù)看源碼蕴坪,還是putval()

if ((p = tab[i = (n -1) & hash]) ==null)

tab[i] = newNode(hash, key, value,null);

通過這兩行代碼我們可以看到 去計算了一次hash值 當(dāng)tab[i]==null時,new了Node元素 放了進(jìn)去 完成了數(shù)據(jù)存儲

如果此時tab[i]不為null則執(zhí)行下面的代碼。

else {

Node e;K k;

if (p.hash == hash &&

((k = p.key) == key || (key !=null && key.equals(k))))

e = p;

在這我們看到? 當(dāng) k的hash值相等且 equals()相等的時候背传,進(jìn)行了覆蓋

else if (pinstanceof TreeNode)

e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

k不相等的時候 如果當(dāng)前Node對象包含了紅黑樹 那么就把它放在了樹里

else {

for (int binCount =0; ; ++binCount) {

if ((e = p.next) ==null) {

p.next = newNode(hash, key, value,null);

if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st?

TREEIFY_THRESHOLD =8

? ? ? ? ? ? ? ? treeifyBin(tab, hash);

break;

}

對單向鏈表進(jìn)行遍歷呆瞻。當(dāng)p.next==null 是說明這是單向鏈表的尾結(jié)點,指針null径玖。記錄單向鏈表的遍歷的次數(shù)痴脾。 TREEIFY_THRESHOLD=8 當(dāng)鏈表長度》=8-1時, 將鏈表轉(zhuǎn)為紅黑樹

if (e.hash == hash &&

((k = e.key) == key || (key !=null && key.equals(k))))

break;

//同樣是進(jìn)行key的hash判斷 一致則覆蓋掉原有值

p = e;

}

}

if (e !=null) {// existing mapping for key

? ? V oldValue = e.value;

if (!onlyIfAbsent || oldValue ==null)

e.value = value;

afterNodeAccess(e);

return oldValue;

}

//覆蓋的過程

++modCount;? ? //記錄put的次數(shù)

if (++size >threshold)

resize();

//重點來了 在此時挺狰,如果數(shù)組需要進(jìn)行擴(kuò)容數(shù)組在什么時候擴(kuò)容呢明郭,就是代碼中的threshold = newThr

在resize()方法中 如果原有tab長度>0且<2的30次方,則tab長度變?yōu)樵瓉淼?lt;1 即原來的2倍

size這個值在哪計算還沒發(fā)現(xiàn) 待我稍后進(jìn)行補(bǔ)充

afterNodeInsertion(evict);

return null;

4.完整的源碼

final V putVal(int hash,K key,V value,boolean onlyIfAbsent,

boolean evict) {

Node[] tab; Node p;int n, i;

if ((tab =table) ==null || (n = tab.length) ==0)

n = (tab = resize()).length;

if ((p = tab[i = (n -1) & hash]) ==null)

tab[i] = newNode(hash, key, value,null);

else {

Node e;K k;

if (p.hash == hash &&

((k = p.key) == key || (key !=null && key.equals(k))))

e = p;

else if (pinstanceof TreeNode)

e = ((TreeNode)p).putTreeVal(this, tab, hash, key, value);

else {

for (int binCount =0; ; ++binCount) {

if ((e = p.next) ==null) {

p.next = newNode(hash, key, value,null);

if (binCount >=TREEIFY_THRESHOLD -1)// -1 for 1st

? ? ? ? ? ? ? ? ? ? ? ? treeifyBin(tab, hash);

break;

}

if (e.hash == hash &&

((k = e.key) == key || (key !=null && key.equals(k))))

break;

p = e;

}

}

if (e !=null) {// existing mapping for 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.2.4三個問題 至于第3個問題 改日在敘丰泊。如有謬誤如疏漏之處煩請各位看官多多指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末薯定,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子瞳购,更是在濱河造成了極大的恐慌话侄,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件学赛,死亡現(xiàn)場離奇詭異年堆,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)盏浇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進(jìn)店門变丧,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人绢掰,你說我怎么就攤上這事痒蓬。” “怎么了滴劲?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵攻晒,是天一觀的道長。 經(jīng)常有香客問我班挖,道長鲁捏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任萧芙,我火速辦了婚禮给梅,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘双揪。我一直安慰自己动羽,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布盟榴。 她就那樣靜靜地躺著曹质,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上羽德,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天几莽,我揣著相機(jī)與錄音,去河邊找鬼宅静。 笑死章蚣,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的姨夹。 我是一名探鬼主播纤垂,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼磷账!你這毒婦竟也來了峭沦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤逃糟,失蹤者是張志新(化名)和其女友劉穎吼鱼,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體绰咽,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡菇肃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了取募。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琐谤。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖玩敏,靈堂內(nèi)的尸體忽然破棺而出斗忌,到底是詐尸還是另有隱情,我是刑警寧澤聊品,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布飞蹂,位于F島的核電站几苍,受9級特大地震影響翻屈,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜妻坝,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一伸眶、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧刽宪,春花似錦厘贼、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春岳掐,著一層夾襖步出監(jiān)牢的瞬間凭疮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工串述, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留执解,地道東北人。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓纲酗,卻偏偏與公主長得像衰腌,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子觅赊,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容