基礎(chǔ)知識—hashmap resize詳解(轉(zhuǎn)載)

雖然在hashmap的原理里面有這段纫雁,但是這個單獨拿出來講rehash或者resize()也是極好的澳盐。

什么時候擴容:當(dāng)向容器添加元素的時候锅论,會判斷當(dāng)前容器的元素個數(shù)讼溺,如果大于等于閾值(知道這個閾字怎么念嗎?不念fa值最易,念yu值四聲)---即當(dāng)前數(shù)組的長度乘以加載因子的值的時候怒坯,就要自動擴容啦。

擴容(resize)就是重新計算容量藻懒,向HashMap對象里不停的添加元素剔猿,而HashMap對象內(nèi)部的數(shù)組無法裝載更多的元素時,對象就需要擴大數(shù)組的長度嬉荆,以便能裝入更多的元素归敬。當(dāng)然Java里的數(shù)組是無法自動擴容的,方法是使用一個新的數(shù)組代替已有的容量小的數(shù)組鄙早,就像我們用一個小桶裝水汪茧,如果想裝更多的水,就得換大水桶限番。

先看一下什么時候舱污,resize();

我們分析下resize的源碼,鑒于JDK1.8融入了紅黑樹弥虐,較復(fù)雜扩灯,為了便于理解我們?nèi)匀皇褂肑DK1.7的代碼媚赖,好理解一些,本質(zhì)上區(qū)別不大驴剔,具體區(qū)別后文再說省古。


這里就是使用一個容量更大的數(shù)組來代替已有的容量小的數(shù)組,transfer()方法將原有Entry數(shù)組的元素拷貝到新的Entry數(shù)組里丧失。


文章中間部分:四豺妓、存儲實現(xiàn);詳細(xì)解釋了為什么indexFor方法中要h & (length-1)

newTable[i]的引用賦給了e.next布讹,也就是使用了單鏈表的頭插入方式琳拭,同一位置上新元素總會被放在鏈表的頭部位置;這樣先放在一個索引上的元素終會被放到Entry鏈的尾部(如果發(fā)生了hash沖突的話)描验,這一點和Jdk1.8有區(qū)別白嘁,下文詳解。在舊數(shù)組中同一條Entry鏈上的元素膘流,通過重新計算索引位置后絮缅,有可能被放到了新數(shù)組的不同位置上。

從上面的for循環(huán)內(nèi)部開始說起吧:詳細(xì)解釋下呼股,這個轉(zhuǎn)存的過程耕魄。和怎么個頭插入法.

Entry e = src[j];

這句話,就把原來數(shù)組上的那個鏈表的引用就給接手了彭谁,所以下面src[j] = null;可以放心大膽的置空吸奴,釋放空間。告訴gc這個地方可以回收啦缠局。

繼續(xù)到do while 循環(huán)里面则奥,

Entry next = e.next;

int i = indexFor(e.hash, newCapacity);計算出元素在新數(shù)組中的位置

下面就是單鏈表的頭插入方式轉(zhuǎn)存元素啦

關(guān)于這個 單鏈表的頭插入方式 的理解,我多說兩句狭园。

這地方我再看的時候读处,就有點蒙了,他到底怎么在插到新的數(shù)組里面的妙啃?

要是在插入新數(shù)組的時候档泽,也出現(xiàn)了一個數(shù)組下標(biāo)的位置處,出現(xiàn)了多個節(jié)點的話揖赴,那又是怎么插入的呢馆匿?

1,假設(shè)現(xiàn)在剛剛插入到新數(shù)組上燥滑,因為是對象數(shù)組渐北,數(shù)組都是要默認(rèn)有初始值的,那么這個數(shù)組的初始值都是null铭拧。不信的可以新建個Javabean數(shù)組測試下赃蛛。

那么e.next = newTable[i],也就是e.next = null啦恃锉。然后再newTable[i] = e;也就是 說這個時候,這個數(shù)組的這個下標(biāo)位置的值設(shè)置成這個e啦呕臂。

2破托,假設(shè)這個時候,繼續(xù)上面的循環(huán)歧蒋,又取第二個數(shù)據(jù)e2的時候土砂,恰好他的下標(biāo)和剛剛上面的那個下標(biāo)相同啦,那么這個時候谜洽,是又要有鏈表產(chǎn)生啦萝映、

e.next = newTable[i];,假設(shè)上面第一次存的叫e1吧阐虚,那么現(xiàn)在e.next = newTable[i];也就是e.next = e1序臂;

然后再,newTable[i] = e;实束,把這個后來的賦值在數(shù)組下標(biāo)為i的位置奥秆,當(dāng)然他們兩個的位置是相同的啦。然后注意現(xiàn)在的e咸灿,我們叫e2吧吭练。e2.next指向的是剛剛的e1,e1的next是null析显。

這就解釋啦:先放在一個索引上的元素終會被放到Entry鏈的尾部。這句話签赃。

關(guān)于什么時候resize()的說明:

看1.7的源碼上說的條件是:

if ((size >= threshold) && (null != table[bucketIndex])) {谷异。。锦聊。}

其中

size表示當(dāng)前hashmap里面已經(jīng)包含的元素的個數(shù)歹嘹。

threshold:threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);

一般是容量值X加載因子。

而1.8的是:

if (++size > threshold){}

其中

size:The number of key-value mappings contained in this map.和上面的是一樣的

threshold:newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);

也是一樣的孔庭,

總結(jié)一下:就是這個map里面包含的元素尺上,也就是size的值,大于等于這個閾值的時候圆到,才會resize()怎抛;

具體到實際情況就是:假設(shè)現(xiàn)在閾值是4;在添加下一個假設(shè)是第5個元素的時候芽淡,這個時候的size還是原來的马绝,還沒加1,size=4挣菲,那么閾值也是4的時候富稻,

當(dāng)執(zhí)行put方法掷邦,添加第5個的時候,這個時候椭赋,4 >= 4抚岗。元素個數(shù)等于閾值。就要resize()啦哪怔。添加第4的時候宣蔚,還是3 >= 4不成立,不需要resize()蔓涧。

經(jīng)過這番解釋件已,可以發(fā)現(xiàn)下面的這個例子,不應(yīng)該是在添加第二個的時候resize()元暴,而是在添加第三個的時候篷扩,才resize()的。

這個也是我后來再細(xì)看的時候茉盏,發(fā)現(xiàn)的鉴未。當(dāng)然,這個咱可以先忽略鸠姨,重點看如何resize()铜秆,以及如何將舊數(shù)據(jù)移動到新數(shù)組的

下面舉個例子說明下擴容過程。

這句話是重點----hash(){return key % table.length;}方法,就是翻譯下面的一行解釋:

假設(shè)了我們的hash算法就是簡單的用key mod 一下表的大醒惹ā(也就是數(shù)組的長度)连茧。

其中的?哈希桶數(shù)組table的size=2, 所以key = 3巍糯、7荣德、5触机,put順序依次為 5、7、3窒升。在mod 2以后都沖突在table[1]這里了侦香。這里假設(shè)負(fù)載因子 loadFactor=1脖卖,即當(dāng)鍵值對的實際大小size 大于 table的實際大小時進行擴容窗声。接下來的三個步驟是哈希桶數(shù)組 resize成4,然后所有的Node重新rehash的過程厌衙。


下面我們講解下JDK1.8做了哪些優(yōu)化距淫。經(jīng)過觀測可以發(fā)現(xiàn),我們使用的是2次冪的擴展(指長度擴為原來2倍)婶希,所以溉愁,

經(jīng)過rehash之后,元素的位置要么是在原位置,要么是在原位置再移動2次冪的位置拐揭。對應(yīng)的就是下方的resize的注釋撤蟆。


看下圖可以明白這句話的意思,n為table的長度堂污,圖(a)表示擴容前的key1和key2兩種key確定索引位置的示例家肯,圖(b)表示擴容后key1和key2兩種key確定索引位置的示例,其中hash1是key1對應(yīng)的哈希值(也就是根據(jù)key1算出來的hashcode值)與高位與運算的結(jié)果盟猖。


元素在重新計算hash之后讨衣,因為n變?yōu)?倍,那么n-1的mask范圍在高位多1bit(紅色)式镐,因此新的index就會發(fā)生這樣的變化:


因此反镇,我們在擴充HashMap的時候,不需要像JDK1.7的實現(xiàn)那樣重新計算hash娘汞,只需要看看原來的hash值新增的那個bit是1還是0就好了歹茶,是0的話索引沒變,是1的話索引變成“原索引+oldCap”你弦,可以看看下圖為16擴充為32的resize示意圖:

我是 沒看懂他連個圖是怎么前后對應(yīng)的惊豺,誰看懂了,交流哈賽禽作。


當(dāng)時上面這個圖尸昧,沒看懂,是因為旷偿,他就沒說每個節(jié)點的hashcode是啥烹俗,他怎么確定是保留在原來的位置,還是說在原來位置的基礎(chǔ)上再加個原來數(shù)組的長度呢萍程。所以衷蜓,上面那個圖僅僅具有丁點兒參考價值。

這個設(shè)計確實非常的巧妙尘喝,既省去了重新計算hash值的時間,而且同時斋陪,由于新增的1bit是0還是1可以認(rèn)為是隨機的朽褪,因此resize的過程,均勻的把之前的沖突的節(jié)點分散到新的bucket了无虚。這一塊就是JDK1.8新增的優(yōu)化點缔赠。有一點注意區(qū)別,JDK1.7中rehash的時候友题,舊鏈表遷移新鏈表的時候嗤堰,如果在新表的數(shù)組索引位置相同,則鏈表元素會倒置度宦,但是從上圖可以看出踢匣,JDK1.8不會倒置告匠。有興趣的同學(xué)可以研究下JDK1.8的resize源碼,寫的很贊离唬。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末后专,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子输莺,更是在濱河造成了極大的恐慌戚哎,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嫂用,死亡現(xiàn)場離奇詭異型凳,居然都是意外死亡,警方通過查閱死者的電腦和手機嘱函,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進店門甘畅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人实夹,你說我怎么就攤上這事橄浓。” “怎么了亮航?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵荸实,是天一觀的道長。 經(jīng)常有香客問我缴淋,道長准给,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任重抖,我火速辦了婚禮露氮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘钟沛。我一直安慰自己畔规,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布恨统。 她就那樣靜靜地躺著叁扫,像睡著了一般。 火紅的嫁衣襯著肌膚如雪畜埋。 梳的紋絲不亂的頭發(fā)上莫绣,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機與錄音悠鞍,去河邊找鬼对室。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的掩宜。 我是一名探鬼主播蔫骂,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼锭亏!你這毒婦竟也來了纠吴?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤慧瘤,失蹤者是張志新(化名)和其女友劉穎戴已,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體锅减,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡糖儡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔匣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片握联。...
    茶點故事閱讀 38,064評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖每瞒,靈堂內(nèi)的尸體忽然破棺而出金闽,到底是詐尸還是另有隱情,我是刑警寧澤剿骨,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布代芜,位于F島的核電站,受9級特大地震影響浓利,放射性物質(zhì)發(fā)生泄漏挤庇。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一贷掖、第九天 我趴在偏房一處隱蔽的房頂上張望嫡秕。 院中可真熱鬧,春花似錦苹威、人聲如沸昆咽。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽掷酗。三九已至,卻和暖如春腹暖,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背翰萨。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工脏答, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓殖告,卻偏偏與公主長得像阿蝶,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子黄绩,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,802評論 2 345

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