IHAVEAQUESTION - JDK1.7 HashMap 鏈表頭插疑問(wèn)?

技術(shù)博客已遷移至個(gè)人頁(yè)侣背,歡迎查看 yloopdaed.icu

您也可以關(guān)注 JPP - 這是一個(gè)Java養(yǎng)成計(jì)劃白华,需要您的加入。


IHAVEAQUESTION

為什么JDK1.7中HashMap鏈表插入時(shí)要在 遍歷完一遍鏈表 后贩耐,再采用頭插法弧腥?

數(shù)組

HashMap在JDK1.7中采用 數(shù)組+鏈表 的存儲(chǔ)結(jié)構(gòu)。

數(shù)組的角標(biāo)是在key值hashCode()的基礎(chǔ)上進(jìn)行多次高位移動(dòng)的擾動(dòng)后盡量保持散列潮太,代碼片段如下:

1 hash

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }
    h ^= k.hashCode();
    // 多次讓高位參與運(yùn)算管搪,擾動(dòng)函數(shù)
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

2 % -> &

采用更搞笑的 &運(yùn)算虾攻。這里length為數(shù)組的長(zhǎng)度,源碼中巧妙的設(shè)計(jì)數(shù)組的長(zhǎng)度必須保持2的整數(shù)冪更鲁。這樣設(shè)計(jì)才能保證length-1計(jì)算后得到 全1 的的二進(jìn)制序列霎箍。

static int indexFor(int h, int length) {
    return h & (length-1);
}

鏈表

數(shù)組的index確認(rèn)后澡为,就可以將鍵值對(duì)插入相應(yīng)位置的鏈表了漂坏。

public V put(K key, V value) {
    ... 
    int hash = hash(key); // hash
    int i = indexFor(hash, table.length); // %
    // 判斷hashmap中有沒(méi)有存在相同的key,如果有的話將這個(gè)key原來(lái)的value覆蓋媒至,并返回
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    ...
    // 擴(kuò)容,頭插
    addEntry(hash, key, value, i);
    return null;
}

上面的源碼部分只保留了關(guān)鍵部分

我們都知道JDK1.7中鏈表的插入方式是頭插筋夏。頭插與尾插相比是節(jié)省了一次鏈表全遍歷的時(shí)間。直接采用下面代碼即可完成:

// 鏈表頭插
table[bucketIndex] = new Entry<>(hash, key, value, table[bucketIndex]);

這部分代碼在put方法的addEntry()中,addEntry()方法在鏈表頭插之前做了擴(kuò)容操作指蚜。

但是奇怪的是涨椒,在上面put方法中有一段循環(huán)遍歷鏈表的代碼摊鸡,這段代碼的目的只是檢查要插入的Key值是否已經(jīng)存在在HashMap中,如果存在就修改蚕冬,同時(shí)將原來(lái)的值返回免猾。

這我就很疑惑了,為什么這里明明已經(jīng)遍歷過(guò)一遍鏈表了囤热,為什么不多寫一個(gè)else猎提,如果沒(méi)有找到存在的Key值,直接將目標(biāo)鍵值對(duì)插入在鏈表尾部呢旁蔼?都已經(jīng)遍歷完了锨苏,插個(gè)值咋了?

可能的原因只能是擴(kuò)容時(shí)機(jī)不好把握棺聊?

HashMap的擴(kuò)容機(jī)制是鍵值對(duì)size超過(guò)閾值后伞租,數(shù)組長(zhǎng)度擴(kuò)充至之前的兩倍,然后將原本下標(biāo)的全部鏈表遷移(這個(gè)遷移的過(guò)程會(huì)倒序鏈表限佩,也可能分散鏈表中的數(shù)據(jù)葵诈,以縮短鏈表的長(zhǎng)度)

void transfer(Entry[] newTable, boolean rehash) {
    int newCapacity = newTable.length;
    for (Entry<K,V> e : table) {
        while(null != e) {
            Entry<K,V> next = e.next;
            ...
            // 同一個(gè)元素轉(zhuǎn)移后的下標(biāo)有兩種情況。
            // 1 與原來(lái)相同 2 在原來(lái)下標(biāo)基礎(chǔ)上加原數(shù)組長(zhǎng)度
            int i = indexFor(e.hash, newCapacity);

            // 這樣遍歷頭插后,鏈表的順序與之前相反
            e.next = newTable[i];
            newTable[i] = e;
            e = next;
        }
    }
}

了解HashMap源碼的朋友可能都知道驯击,這個(gè)擴(kuò)容和遷移的代碼在高并發(fā)時(shí)并不是線程安全的烁兰。可能出現(xiàn)循環(huán)鏈表徊都,以至于get時(shí)陷入死循環(huán)沪斟。

也許正因如此,需要將鏈表插入和擴(kuò)容的代碼從之前的循環(huán)中獨(dú)立出來(lái)暇矫。并采用頭插的方式主之,盡量再循環(huán)一次鏈表。

最后

以上都是我在閱讀HashMap源碼后產(chǎn)生疑問(wèn)李根,獨(dú)立思考槽奕,自我解答的過(guò)程。

可能是很少有人產(chǎn)生跟我相似的疑問(wèn)房轿,所以我在網(wǎng)上也沒(méi)能查找到準(zhǔn)確的資料和答案粤攒。

所以以上全是自己的推斷和猜測(cè)。畢竟HashMap源碼不論是在數(shù)據(jù)結(jié)構(gòu)還是算法思想層面都是非常優(yōu)雅的囱持。別人這么設(shè)計(jì)肯定是有原因的夯接。哈哈

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市纷妆,隨后出現(xiàn)的幾起案子盔几,更是在濱河造成了極大的恐慌,老刑警劉巖掩幢,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逊拍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡际邻,警方通過(guò)查閱死者的電腦和手機(jī)芯丧,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)枯怖,“玉大人注整,你說(shuō)我怎么就攤上這事《认酰” “怎么了肿轨?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)蕊程。 經(jīng)常有香客問(wèn)我椒袍,道長(zhǎng),這世上最難降的妖魔是什么藻茂? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任驹暑,我火速辦了婚禮玫恳,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘优俘。我一直安慰自己京办,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布帆焕。 她就那樣靜靜地躺著惭婿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪叶雹。 梳的紋絲不亂的頭發(fā)上财饥,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天,我揣著相機(jī)與錄音折晦,去河邊找鬼钥星。 笑死,一個(gè)胖子當(dāng)著我的面吹牛满着,可吹牛的內(nèi)容都是我干的谦炒。 我是一名探鬼主播,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼风喇,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼编饺!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起响驴,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撕蔼,沒(méi)想到半個(gè)月后豁鲤,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡鲸沮,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年琳骡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片讼溺。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡楣号,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出怒坯,到底是詐尸還是另有隱情炫狱,我是刑警寧澤,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布剔猿,位于F島的核電站视译,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏归敬。R本人自食惡果不足惜酷含,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一鄙早、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧椅亚,春花似錦限番、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至别威,卻和暖如春躯舔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背省古。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工粥庄, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豺妓。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓惜互,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親琳拭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子训堆,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345