Jdk1.8版本的 HashMap

之前看過一下其他的文章若债,有幾篇寫的比較好的充坑,記得是美團(tuán)的技術(shù)團(tuán)隊(duì)分享的,結(jié)合自己看源碼做的筆記, 之前都只是作為自己的筆記存起來的,現(xiàn)在分享出來泽本,可能有人會(huì)看,哈哈淘太,太自戀了,筆記里面有些是其他 文章的分析规丽,圖是我自己整理的蒲牧,要是有寫分析借鑒了你的文章沒注明出處,先道歉,因?yàn)楦舻奶昧硕妮海挥浀檬窃谀目吹奈恼吕脖馈:?jiǎn)書的富文本編輯不能像有道筆記那樣可以顯示顏色的,我就先截長(zhǎng)圖吧艘狭。

Jdk8版本的源碼改動(dòng)很多挎扰,還引入了紅黑樹的數(shù)據(jù)結(jié)構(gòu),具體幾個(gè)方法還是看源碼吧,因?yàn)槭枪P記,所以方法里面很多不重要的,比如拋異常的就沒記到筆記巢音,或者是用偽代碼代替遵倦。


int DEFAULT_INITIAL_CAPACITY = 16   //初始容量
int MAXIMUM_CAPACITY = 1 << 30; //最大容量
float DEFAULT_LOAD_FACTOR = 0.75f;  //負(fù)載因子
int TREEIFY_THRESHOLD = 8;  //需要用樹存儲(chǔ)時(shí)的臨界值
int UNTREEIFY_THRESHOLD = 6;    
int MIN_TREEIFY_CAPACITY = 64;  //樹的最低的容量

public V put(K key, V value) {
    return putVal(hash(key), key, value, false, true);
}
static final int hash(Object key) {
    int h;
    // h = key.hashCode() 為第一步 取hashCode值
    // h ^ (h >>> 16)  為第二步 高位參與運(yùn)算
    // h>>>16 是右移 16位, ^ 是異或運(yùn)算, 只有 0 ^ 1 或者 1^0 才是1,其他為 0
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
final V putVal(int hash, K key, V value, boolean onlyIfAbsent,
               boolean evict) {
    Node<K,V>[] tab; Node<K,V> p; int n, i; //p是計(jì)算出來的下標(biāo)所在的結(jié)點(diǎn), i 是插入下標(biāo), n 是 table 長(zhǎng)度
    if ((tab = table) == null || (n = tab.length) == 0) //table長(zhǎng)度為 32
        n = (tab = resize()).length;
    if ((p = tab[i = (n - 1) & hash]) == null)      
        //這里是取模運(yùn)算= hash % n
        //key的哈希碼與 n-1 相與, 得到下標(biāo), n-1 可以減少哈希沖突概率,因?yàn)?^n-1變成2進(jìn)制會(huì)有很多個(gè) 1
        // 如果是2^n變成二進(jìn)制會(huì)有很多個(gè) 0, 0跟什么相與都是0,1跟 跟 0港谊, 1相與會(huì)得到對(duì)應(yīng)的結(jié)果
        tab[i] = newNode(hash, key, value, null);  //如果該下標(biāo)結(jié)點(diǎn)為空, 直接插入結(jié)點(diǎn)
    else {
        Node<K,V> e; K k;
        if (p.hash == hash && ((k = p.key) == key || (key != null && key.equals(k))))
            e = p;  // 如果計(jì)算出來的數(shù)組下標(biāo)已經(jīng)有元素了, 直接用新的 value 替換該結(jié)點(diǎn)的 value
        else if (p instanceof TreeNode)
                   e = ((TreeNode<K,V>)p).putTreeVal(this, tab, hash, key, value);
        else {  //查詢到鏈表的最后一個(gè)節(jié)點(diǎn)也沒有找到,那么新建一個(gè)Node,然后加到最后一個(gè)元素的后面
            for (int binCount = 0; ; ++binCount) {
                if ((e = p.next) == null) { //找到的待插入結(jié)點(diǎn) p 的后驅(qū)結(jié)點(diǎn)為空
                    p.next = newNode(hash, key, value, null);   //將新的結(jié)點(diǎn)插入到 p.next, p是最后的結(jié)點(diǎn)
                    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;  //如果在這個(gè)鏈表上找到與插入結(jié)點(diǎn)相等的結(jié)點(diǎn)骇吭,則不插入
                p = e;  //否則繼續(xù)在第一個(gè)結(jié)點(diǎn)的后驅(qū)結(jié)點(diǎn)繼續(xù)查找比較
            }
        }
        if (e != null) {
            V oldValue = e.value;
            if (!onlyIfAbsent || oldValue == null) { e.value = value; }
            afterNodeAccess(e);
            // 如果計(jì)算出來的數(shù)組下標(biāo)已經(jīng)有元素了, 直接用新的 value 替換該結(jié)點(diǎn)的 value
            //并返回舊的 value
            return oldValue;
        }
    }
    ++modCount;
    if (++size > threshold)
            resize();
    afterNodeInsertion(evict);
    return null;
}
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) {   //將舊的容量翻倍, 舊的 臨界值翻倍
       if ((newCap = oldCap << 1) < MAXIMUM_CAPACITY && oldCap >= DEFAULT_INITIAL_CAPACITY) {
                newThr = oldThr << 1; // double threshold
        }
    }
    else if (oldThr > 0) { newCap = oldThr; }   //舊的 臨界值變?yōu)樾碌娜萘?    else {               // 設(shè)置默認(rèn)容量, 臨界值, 這里只出現(xiàn)在 第一次put就擴(kuò)容的時(shí)候
        newCap = DEFAULT_INITIAL_CAPACITY;
        newThr = (int)(DEFAULT_LOAD_FACTOR * DEFAULT_INITIAL_CAPACITY);
    }
    threshold = newThr;
    Node<K,V>[] newTab = (Node<K,V>[])new Node[newCap];
    table = newTab;
    if (oldTab == null) {
        return newTab;
    }
    // 這里的數(shù)組遷移比較消耗性能, 只需要把數(shù)組每個(gè)下標(biāo)中有元素的結(jié)點(diǎn)直接放到新數(shù)組下標(biāo)中,結(jié)點(diǎn)的引用鏈表還是沒變
    for (int j = 0; j < oldCap; ++j) {  //將舊的哈希表復(fù)制到新的哈希表,oldCap是數(shù)組容量
          if ( ( (Node<K,V>) e = oldTab[j] ) == null ) { 
               continue; 
          }
          oldTab[j] = null;
          if (e.next == null)   //舊的哈希表對(duì)應(yīng)的下標(biāo)只有一個(gè)結(jié)點(diǎn)時(shí),用哈希值重新計(jì)算新容量的下標(biāo)
               newTab[e.hash & (newCap - 1)] = e;
          else if (e instanceof TreeNode)
               ((TreeNode<K,V>)e).split(this, newTab, j, oldCap);
          else { // preserve order
                Node<K,V> loHead, loTail; hiHead, hiTail ; //all = null
                Node<K,V> next;
                do {
                     next = e.next;
                           // n=16  二進(jìn)制為  0 0 0 1  0 0 0 0
                     // n=16 n-1=15 二進(jìn)制為  0 0 0 0  1 1 1 1
                     // n=32 n-1=31 二進(jìn)制為  0 0 0 1  1 1 1 1
                   //原來的下標(biāo)為 5  二進(jìn)制為  0 0 0 0  0 1 0 1
                     // 5&16=0,說明下標(biāo)為5 的下劃線高位是0,與新容量31相與還是5
                     // 所以只需要知道原下標(biāo)的高位如果是0則使用原來的下標(biāo)歧寺,是1則原下標(biāo)+oldCap
                     if ((e.hash & oldCap) == 0) {  //判斷原下標(biāo)的高位是否為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) { //原下標(biāo)高位是 0 則使用原下標(biāo)
                      loTail.next = null; 
                      newTab[j] = loHead; 
                }
                if (hiTail != null) { 
                     hiTail.next = null; 
                     newTab[j + oldCap] = hiHead; 
                }
          }
    }
    return newTab;
}
final void treeifyBin(Node<K,V>[] tab, int hash) {
    int n, index; Node<K,V> e;
    if (tab == null || (n = tab.length) < MIN_TREEIFY_CAPACITY)
        resize();
    else if ((e = tab[index = (n - 1) & hash]) != null) {
        TreeNode<K,V> hd = null, tl = null;
        do {     //將普通的鏈表結(jié)點(diǎn) Node 轉(zhuǎn)化為 樹結(jié)點(diǎn), 保存 hash, key, value
            TreeNode<K,V> p = replacementTreeNode(e, null);
            if (tl == null)
                hd = p;
            else 
                p.prev = tl;
                tl.next = p;
            tl = p;
        } while ((e = e.next) != null);
        //上面的循環(huán)目的是將 下標(biāo)對(duì)應(yīng)的鏈表的 Node 全部替換為 樹結(jié)點(diǎn) TreeNode
        if ((tab[index] = hd) != null){
            hd.treeify(tab);    //將構(gòu)造好的 TreeNode 鏈表轉(zhuǎn)為紅黑樹, 提升查詢時(shí)間復(fù)雜度
        }
    }
}
public V get(Object key) {
    Node<K,V> e;
    return (e = getNode(hash(key), key)) == null ? null : e.value;
}

final Node<K,V> getNode(int hash, Object key) {
    Node<K,V>[] tab; Node<K,V> first, e; int n; K k;
    if ((tab = table) != null && (n = tab.length) > 0 && (first = tab[(n - 1) & hash]) != null) {
        if (first.hash == hash && ((k = first.key) == key || (key != null && key.equals(k)))){
            //先根據(jù)key算到 hash值燥狰,算出下標(biāo)棘脐,如果下標(biāo)的第一個(gè)結(jié)點(diǎn)的 hash值相等
            //并且key相等就直接返回第一個(gè) 結(jié)點(diǎn)
            return first; 
        }
        if ((e = first.next) != null) {
            if (first instanceof TreeNode) {
                return ((TreeNode<K,V>)first).getTreeNode(hash, key);
            }
            do {    //否則逐個(gè)對(duì)比下標(biāo)上的鏈表結(jié)點(diǎn),相等條件為 hash, key相等
                if (e.hash == hash && ((k = e.key) == key || (key != null && key.equals(k)))) {
                    return e;
                }
            } while ((e = e.next) != null);
        }
    }
    return null;
}
image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市龙致,隨后出現(xiàn)的幾起案子蛀缝,更是在濱河造成了極大的恐慌,老刑警劉巖目代,帶你破解...
    沈念sama閱讀 212,222評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屈梁,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡榛了,警方通過查閱死者的電腦和手機(jī)在讶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來霜大,“玉大人构哺,你說我怎么就攤上這事≌嚼ぃ” “怎么了曙强?”我有些...
    開封第一講書人閱讀 157,720評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)途茫。 經(jīng)常有香客問我碟嘴,道長(zhǎng),這世上最難降的妖魔是什么囊卜? 我笑而不...
    開封第一講書人閱讀 56,568評(píng)論 1 284
  • 正文 為了忘掉前任娜扇,我火速辦了婚禮,結(jié)果婚禮上边败,老公的妹妹穿的比我還像新娘袱衷。我一直安慰自己,他們只是感情好笑窜,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評(píng)論 6 386
  • 文/花漫 我一把揭開白布致燥。 她就那樣靜靜地躺著,像睡著了一般排截。 火紅的嫁衣襯著肌膚如雪嫌蚤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評(píng)論 1 290
  • 那天断傲,我揣著相機(jī)與錄音脱吱,去河邊找鬼。 笑死认罩,一個(gè)胖子當(dāng)著我的面吹牛箱蝠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評(píng)論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼宦搬,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼牙瓢!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起间校,我...
    開封第一講書人閱讀 37,773評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤矾克,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后憔足,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體胁附,經(jīng)...
    沈念sama閱讀 44,220評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評(píng)論 2 327
  • 正文 我和宋清朗相戀三年滓彰,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了控妻。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,697評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡揭绑,死狀恐怖饼暑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情洗做,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評(píng)論 4 332
  • 正文 年R本政府宣布彰居,位于F島的核電站诚纸,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏陈惰。R本人自食惡果不足惜畦徘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評(píng)論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望抬闯。 院中可真熱鬧井辆,春花似錦、人聲如沸溶握。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽睡榆。三九已至萍肆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間胀屿,已是汗流浹背塘揣。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留宿崭,地道東北人亲铡。 一個(gè)月前我還...
    沈念sama閱讀 46,433評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親奖蔓。 傳聞我的和親對(duì)象是個(gè)殘疾皇子赞草,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評(píng)論 2 350

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

  • 前言 上篇文章講解了JDK1.7中的HashMap源碼, 主要采用數(shù)組+鏈表來實(shí)現(xiàn), 根據(jù)元素的hash計(jì)算出來的...
    海之韻Baby閱讀 357評(píng)論 0 0
  • 先推薦一個(gè)看java源碼的網(wǎng)站 grepcode.com提供各種不同版本的源碼在線查看和下載,而且有語法高亮锭硼,函數(shù)...
    鍋與盆閱讀 9,808評(píng)論 5 19
  • 前言 HashMap 在 Java 和 Android 開發(fā)中非常常見 而HashMap 1.8 相對(duì)于 Ha...
    Carson帶你學(xué)安卓閱讀 21,985評(píng)論 15 188
  • 最好自己愛自己房资,那樣輕松。 若讓別人愛自己檀头,總會(huì)先慢慢愛別人轰异,然后求別人來愛自己。我?guī)е胍粣鄣男睦砣?duì)別人好和...
    斜錯(cuò)刀閱讀 233評(píng)論 0 0
  • 我端詳著那只桃子 一半青黃 一半紅 腦袋里充斥著 白雪公主的故事 啊 我們的智慧 讓我們可以抉擇 連同吃桃子這...
    天寒閱讀 145評(píng)論 1 2