HashMap源碼分析

概述

本文源碼針對(duì)Java8的HashMap墩蔓。HashMap內(nèi)部是由數(shù)組+鏈表或紅黑樹的結(jié)構(gòu)實(shí)現(xiàn)的咧织。HashMap默認(rèn)初始化數(shù)組大小為16,負(fù)載因子是0.75,初始閥值12,每當(dāng)數(shù)組元素的數(shù)量超過閥值后會(huì)擴(kuò)容胸蛛,每次擴(kuò)容為舊空間大小的一倍狗准,閥值也是增大一倍蔼囊。

put()

put()先判斷要放入的值是否會(huì)哈希沖突放接,如果不哈希沖突冈欢,則直接在hash到的位置插入椿胯。
如果發(fā)生沖突迈喉,判斷是是鏈表還是紅黑樹仑扑,鏈表的話是直接插入到鏈尾放航,如果插入后鏈表長(zhǎng)度大于8盗飒,會(huì)將鏈表轉(zhuǎn)化為紅黑樹(哈希數(shù)組長(zhǎng)度>=64)或者進(jìn)行擴(kuò)容;如果是紅黑樹嚷量,則按紅黑樹插入節(jié)點(diǎn)的規(guī)則來處理。
存放好值后逆趣,會(huì)判斷哈希數(shù)組的大小是否超過閥值蝶溶,超過的話進(jìn)行擴(kuò)容處理。

final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
                   boolean evict) {
        Node<K,V>[] tab; Node<K,V> p; int n, i;
        if ((tab = table) == null || (n = tab.length) == 0)
            n = (tab = resize()).length;
        if ((p = tab[i = (n - 1) & hash]) == null)
            /** 若hash所在的位置為空宣渗,則創(chuàng)建新節(jié)點(diǎn)并插入到該位置 */
            tab[i] = newNode(hash, key, value, null);
        else {
            /** 若hash所在的位置不為空抖所,則處理沖突 */
            Node<K,V> e; K k;
            if (p.hash == hash &&
                ((k = p.key) == key || (key != null && key.equals(k))))
                /** 如果原有節(jié)點(diǎn)的hash值和要插入的節(jié)點(diǎn)的hash值一致,先記錄原有節(jié)點(diǎn)痕囱,
                 * 具體是覆蓋還是不處理下面代碼進(jìn)行 */
                e = p;
            else if (p instanceof TreeNode)
                /** 樹結(jié)構(gòu)節(jié)點(diǎn)的處理 */
                e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
            else {
                /** 鏈表結(jié)構(gòu)節(jié)點(diǎn)的處理 */
                for (int binCount = 0; ; ++binCount) {
                    if ((e = p.next) == null) {
                        /** 新插入的節(jié)點(diǎn)在鏈尾 */
                        p.next = newNode(hash, key, value, null);
                        if (binCount >= TREEIFY_THRESHOLD - 1) // -1 for 1st
                            /** 如果插入后的鏈表長(zhǎng)度大于8田轧,則將鏈表轉(zhuǎn)化為紅黑樹,也有可能是擴(kuò)容哈希數(shù)組 */
                            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;
                /**  onlyIfAbsent為false時(shí)或oldValue為空時(shí)會(huì)覆蓋舊值
                 * 意思如果onlyIfAbsend為true鞍恢,但原來的oldValue為空時(shí)傻粘,oldValue還是會(huì)被覆蓋為新值
                 * 就是只要oldValue為空,不管onlyIfAbsend的值如何帮掉,oldValue會(huì)被覆蓋成新值*/
                if (!onlyIfAbsent || oldValue == null)
                    e.value = value;
                afterNodeAccess(e);
                return oldValue;
            }
        }
        ++modCount;
        /** 如果size自增后大于閥值弦悉,進(jìn)行擴(kuò)容處理 */
        if (++size > threshold)
            resize();
        afterNodeInsertion(evict);
        return null;
    }
resize()

擴(kuò)容方法,每次擴(kuò)容的大小都是前面大小的一倍旭寿。擴(kuò)容后是需要重新hash確定每個(gè)元素的位置警绩。
如果當(dāng)前元素不是鏈表或紅黑樹,直接存放到新的hash位置盅称;
如果是鏈表肩祥,則把鏈表拆分成低位鏈表和高位鏈表,低位鏈表還是在原來的位置缩膝,高位鏈表則存放到新位置上混狠;
如果是紅黑樹,也需要拆分成低位和高位鏈表疾层,不同的是會(huì)根據(jù)拆分后的鏈表長(zhǎng)度是否小于等于6将饺,則會(huì)將對(duì)應(yīng)的樹結(jié)構(gòu)退化為鏈表。

/**調(diào)整存儲(chǔ)空間大小 */
    final Node<K,V>[] resize() {
        Node<K,V>[] oldTab = table;
        int oldCap = (oldTab == null) ? 0 : oldTab.length;
        int oldThr = threshold;
        int newCap, newThr = 0;
        if (oldCap > 0) {
            /**如果舊數(shù)組容量比最大容量(2^30)還大,則擴(kuò)容閥值設(shè)為Integer.MAX_VALUE(2^31 - 1) */
            if (oldCap >= MAXIMUM_CAPACITY) {
                threshold = Integer.MAX_VALUE;
                /**直接返回舊數(shù)組 */
                return oldTab;
            }
            /** 擴(kuò)容一倍后的新容量小于最大容量且原來舊數(shù)組容量是大于初始容量(16)予弧,則新閥值=舊閥值*2 */
            else if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY &&
                     oldCap >= DEFAULT_INITIAL_CAPACITY)
                newThr = oldThr << 1; // double threshold
        }
        else if (oldThr > 0) // initial capacity was placed in threshold
            /** 如果舊數(shù)組容量非正數(shù)刮吧,且舊閥值大于0,則新容量=舊閥值 */
            newCap = oldThr;
        else {               // zero initial threshold signifies using defaults
            /** 如果舊數(shù)組容量和舊閥值都為0掖蛤,則新容量=16杀捻,新閥值=16*0.75=12; */
            newCap = DEFAULT_INITIAL_CAPACITY;
            newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
        }
        if (newThr == 0) {
            float ft = (float)newCap * loadFactor;
            newThr = (newCap < MAXIMUM_CAPACITY && ft < (float)MAXIMUM_CAPACITY ?
                      (int)ft : Integer.MAX_VALUE);
        }
        threshold = newThr;
        /**下面代碼是創(chuàng)建新擴(kuò)充容量長(zhǎng)度的數(shù)組,并且把舊數(shù)組的值移動(dòng)到新數(shù)組*/
        @SuppressWarnings({"rawtypes","unchecked"})
            Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
        table = newTab;
        if (oldTab != null) {
            for (int j = 0; j < oldCap; ++j) {
                Node<K,V> e;
                if ((e = oldTab[j]) != null) {
                    oldTab[j] = null;
                    if (e.next == null)
                        /**如果當(dāng)前節(jié)點(diǎn)沒有下一個(gè)鏈表節(jié)點(diǎn)蚓庭,則在新數(shù)組中重新hash找到對(duì)應(yīng)的位置存放  */
                        newTab[e.hash & (newCap - 1)] = e;
                    else if (e instanceof TreeNode)
                        /**如果當(dāng)前節(jié)點(diǎn)是一顆紅黑樹的根節(jié)點(diǎn)致讥,則按紅黑樹的規(guī)則存放進(jìn)新數(shù)組  */
                        ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
                    else { // preserve order
                        /**如果當(dāng)前節(jié)點(diǎn)有下一個(gè)鏈表節(jié)點(diǎn),是一個(gè)單向鏈表的頭節(jié)點(diǎn)  */
                        Node<K,V> loHead = null, loTail = null;
                        Node<K,V> hiHead = null, hiTail = null;
                        Node<K,V> next;
                        /**這個(gè)循環(huán)是找出兩個(gè)鏈表器赞,滿足(e.hash & oldCap) == 0這個(gè)條件的放到lo低位鏈表中垢袱,
                         * 不滿足的,放到hi高位鏈表中港柜,lo鏈表在新數(shù)組中的位置和舊數(shù)組一樣请契,而hi鏈表在新數(shù)組中的位置
                         * = 舊數(shù)組中的位置j + 舊數(shù)組的容量oldcap */
                        do {
                            next = e.next;
                            /**這個(gè)條件的含義是若節(jié)點(diǎn)的哈希值和舊長(zhǎng)度相與運(yùn)算后還是0,說明該哈希值小于舊表的長(zhǎng)度潘懊,則在新表中即使重新哈希姚糊,位置也是和舊表一樣  */
                            if ((e.hash & oldCap) == 0) {
                                if (loTail == null)
                                    loHead = e;
                                else
                                    loTail.next = e;
                                loTail = e;
                            }
                            else {
                                if (hiTail == null)
                                    hiHead = e;
                                else
                                    hiTail.next = e;
                                hiTail = e;
                            }
                        } while ((e = next) != null);
                        if (loTail != null) {
                            loTail.next = null;
                            newTab[j] = loHead;
                        }
                        if (hiTail != null) {
                            hiTail.next = null;
                            newTab[j + oldCap] = hiHead;
                        }
                    }
                }
            }
        }
        return newTab;
    }
get()

獲取元素的流程相對(duì)簡(jiǎn)單,根據(jù)hash和key值查找數(shù)組授舟,如果根據(jù)hash值找到的元素的key值不相等救恨,則從當(dāng)前元素所在的鏈表或者紅黑樹中去查找。

final Node<K,V> getNode(int hash, Object key) {
        Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
        /** 表不為空且長(zhǎng)度大于0并且hash所在的位置不為空的情況下释树,才會(huì)繼續(xù)判斷下述情況 */
        if ((tab = table) != null && (n = tab.length) > 0 &&
            (first = tab[(n - 1) & hash]) != null) {
            /** hash值和key都一致肠槽,就是要找的節(jié)點(diǎn) */
            if (first.hash == hash && // always check first node
                ((k = first.key) == key || (key != null && key.equals(k))))
                return first;
            if ((e = first.next) != null) {
                /** 從紅黑樹中找 */
                if (first instanceof TreeNode)
                    return ((TreeNode<K,V>)first).getTreeNode(hash, key);
                /** 從鏈表中找 */
                do {
                    if (e.hash == hash &&
                        ((k = e.key) == key || (key != null && key.equals(k))))
                        return e;
                } while ((e = e.next) != null);
            }
        }
        return null;
    }
Java8的HashMap相對(duì)于Java7的優(yōu)化或區(qū)別

1、Java8的HashMap在鏈表長(zhǎng)度大于或等于8時(shí)奢啥,會(huì)將鏈表轉(zhuǎn)化為紅黑樹結(jié)構(gòu)秸仙,提高了查詢的效率;
2桩盲、Java8的HashMap存入元素發(fā)生沖突時(shí)寂纪,是將元素插入到鏈表,而Java7是插入到鏈頭赌结;同時(shí)在擴(kuò)容時(shí)捞蛋,Java7是把鏈表拆分成兩個(gè)倒置,而Java8是將鏈表拆分成低位和高位鏈表柬姚,低位鏈表在舊位置拟杉,高位鏈表在新位置;這個(gè)處理避免了Java7在多線程中出現(xiàn)鏈表循環(huán)引用的情況量承;
3搬设、Java8的哈希算法和Java7不一樣穴店。

HashMap是否線程安全

HashMap不是線程安全的,有線程安全的容器替代ConcurrentHashMap拿穴。ConcurrentHashMap是通過CAS+synchronized來實(shí)現(xiàn)線程同步的泣洞。

完整的源碼注釋文件

源碼注釋文件

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市贞言,隨后出現(xiàn)的幾起案子斜棚,更是在濱河造成了極大的恐慌,老刑警劉巖该窗,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異蚤霞,居然都是意外死亡酗失,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門昧绣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來规肴,“玉大人,你說我怎么就攤上這事夜畴⊥先校” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵贪绘,是天一觀的道長(zhǎng)兑牡。 經(jīng)常有香客問我,道長(zhǎng)税灌,這世上最難降的妖魔是什么均函? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮菱涤,結(jié)果婚禮上苞也,老公的妹妹穿的比我還像新娘。我一直安慰自己粘秆,他們只是感情好如迟,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著攻走,像睡著了一般殷勘。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上陋气,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天劳吠,我揣著相機(jī)與錄音,去河邊找鬼巩趁。 笑死痒玩,一個(gè)胖子當(dāng)著我的面吹牛淳附,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蠢古,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼奴曙,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了草讶?” 一聲冷哼從身側(cè)響起洽糟,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎堕战,沒想到半個(gè)月后坤溃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘱丢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年薪介,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片越驻。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡汁政,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出缀旁,到底是詐尸還是另有隱情记劈,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布并巍,位于F島的核電站目木,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏履澳。R本人自食惡果不足惜嘶窄,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望距贷。 院中可真熱鬧柄冲,春花似錦、人聲如沸忠蝗。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阁最。三九已至戒祠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間姜盈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工馏颂, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留示血,地道東北人救拉。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像亿絮,于是被迫代替她去往敵國(guó)和親告喊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子派昧,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

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

  • HashMap 是 Java 面試必考的知識(shí)點(diǎn),面試官從這個(gè)小知識(shí)點(diǎn)就可以了解我們對(duì) Java 基礎(chǔ)的掌握程度蒂萎。網(wǎng)...
    野狗子嗷嗷嗷閱讀 6,661評(píng)論 9 107
  • 移步數(shù)據(jù)結(jié)構(gòu)--容器匯總(java & Android)由于 HashMap 底層涉及到太多方面,一篇文章總是不能...
    凱玲之戀閱讀 829評(píng)論 0 5
  • HashMap源碼分析 HashMap是對(duì)Map接口的一種實(shí)現(xiàn),底層數(shù)據(jù)結(jié)構(gòu)使用了散列表(Hash table)实苞。...
    Leocat閱讀 419評(píng)論 0 0
  • 0、前言 HashMap是Java中最常用的集合之一黔牵,相信大家都使用過聪轿,可是你對(duì)他了解嗎?HashMap存儲(chǔ)的數(shù)據(jù)...
    developer_chang閱讀 445評(píng)論 0 4
  • hash概念 把關(guān)鍵字通過某個(gè)函數(shù)映射到得到一個(gè)固定值猾浦,然后這個(gè)固定值來確定數(shù)組中的某個(gè)位置陆错,通過數(shù)組下標(biāo)一次定位...
    阿貍404閱讀 398評(píng)論 0 4