ConcurrentHashMap源碼分析(JDK8) 遍歷操作分析

圖解遍歷過程

說明:下文中的tab表示源table。nextTable表示擴容時,遷移的目標table

1 當遍歷到fwd節點的時候,說明正在擴容,此節點的數據已經遷移到了nextTable

image.png

2 將tab1的遍歷狀態(tab1當前的遍歷索引index,tab1的長度等信息)push到stack,然后遍歷遷移到nextTable(tab2)中的hash桶

(1)由于擴容時,會將tab(tab1)中索引為index的桶,遷移到nextTable(tab2中索引為index和索引為index+tab.length的2個桶中。因此在nextTable(tab2)中的遍歷順序為:index,index+tab.length (tab2中的桶2,和桶4 )

(2)如果桶2和桶4都是正常的節點,遍歷完桶2和桶4后,就會將stack中的tab(tab1)彈出,繼續遍歷tab(tab1)中的桶

(3)但是桶2是fwd節點,說明tab2也被擴容,此節點的node被遷移到了nextTable(tab3)中

image.png

3 將tab2的遍歷狀態push到stack,跳到nextTable(tab3)中繼續遍歷

(1) 按照擴容遷移規則,先遍歷index=2的hash桶

image.png

(2)然后遍歷index=6的hash桶(index+tab2.length=2+4=6)


image.png

4 遍歷完tab3后,將tab2出棧,根據記錄的遍歷信息,繼續遍歷tab2。

(1)從stack彈出的tab2,進入了spare棧。(spare棧似乎是為了對象重用,減少new對象的內存開銷)

(2)彈出的tab2棧節點,記錄了tab2的當前index=2。然后接著遍歷下一個index=4的桶(index+tab1.length=2+2=4)

(3)tab2的桶4依舊是個fwd節點,那么繼續將tab2入棧(此時tab2的index=4)

(4)桶4依舊是一個fwd節點,那么繼續將tab2的遍歷狀態push到stack,然后遍歷nextTable(tab3)

(5) 此時入棧的tab2遍歷狀態節點,并不是重新new的,而是spare所指向的節點

image.png

5 遍歷nextTable(tab3)中的桶4(index=4)和桶8(index+tab2.length)

image.png

6 完成了nextTable(tab3)的遍歷,彈出tab2的遍歷狀態節點。

(1) nextTable(tab2)遍歷完畢。

image.png

7 彈出tab(tab1), 開始繼續遍歷tab1

(1) stack棧空了,而且spare棧按照出棧的順序保存了相關節點

(2) 遍歷完畢。

image.png

相關源碼分析

ConcurrentHashMap的遍歷操作,主要是通過如下迭代器實現: KeyIterator,EntryIterator,ValueIterator

類圖如下:


image.png

Traverser類

 static class Traverser<K,V> {
        Node<K,V>[] tab;        // current table; updated if resized
        //下一個要訪問的entry
        Node<K,V> next;         // the next entry to use
        //發現forwardingNode時,保存當前tab相關信息
        TableStack<K,V> stack, spare; // to save/restore on ForwardingNodes
        //下一個要訪問的hash桶索引
        int index;              // index of bin to use next
        //當前正在訪問的初始tab的hash桶索引
        int baseIndex;          // current index of initial table
        //初始tab的hash桶索引邊界
        int baseLimit;          // index bound for initial table
        //初始tab的長度
        final int baseSize;     // initial table size
        
        ...
        ...
        
        /**
         * 如果有可能,返回下一個有效節點,否則返回null。
         */
        final Node<K,V> advance() {
            Node<K,V> e;
            //獲取Node鏈表的下一個元素e
            if ((e = next) != null)
                e = e.next;
            for (;;) {
                Node<K,V>[] t; int i, n;  // must use locals in checks
                //e不為空,返回e
                if (e != null)
                    return next = e;
                    
                //e為空,說明此鏈表已經遍歷完成,準備遍歷下一個hash桶
                if (baseIndex >= baseLimit || (t = tab) == null ||
                    (n = t.length) <= (i = index) || i < 0)
                    //到達邊界,返回null
                    return next = null;
                    
                //獲取下一個hash桶對應的node鏈表的頭節點
                if ((e = tabAt(t, i)) != null && e.hash < 0) {
                    //轉發節點,說明此hash桶中的節點已經遷移到了nextTable
                    if (e instanceof ForwardingNode) {
                        tab = ((ForwardingNode<K,V>)e).nextTable;
                        e = null;
                        //保存當前tab的遍歷狀態
                        pushState(t, i, n);
                        continue;
                    }
                    //紅黑樹
                    else if (e instanceof TreeBin)
                        e = ((TreeBin<K,V>)e).first;
                    else
                        e = null;
                }
                
                
                if (stack != null)
                    //此時遍歷的是遷移目標nextTable,嘗試回退到源table,繼續遍歷源table中的節點
                    recoverState(n);
                else if ((index = i + baseSize) >= n)
                    //初始tab的hash桶索引+1 ,即遍歷下一個hash桶
                    index = ++baseIndex; // visit upper slots if present
            }
        }
        
        
          /**
         * 在遇到轉發節點時保存遍歷狀態。
         */
        private void pushState(Node<K,V>[] t, int i, int n) {
            TableStack<K,V> s = spare;  // reuse if possible
            if (s != null)
                spare = s.next;
            else
                s = new TableStack<K,V>();
            s.tab = t;
            s.length = n;
            s.index = i;
            s.next = stack;
            stack = s;
        }

        /**
         * 可能會彈出遍歷狀態。
         * @param n length of current table
         */
        private void recoverState(int n) {
            TableStack<K,V> s; int len;
            /**
              (s = stack) != null :stack不空,說明此時遍歷的是nextTable  
              (index += (len = s.length)) >= n: 確保了按照index,index+tab.length的順序遍歷nextTable,條件成立表示nextTable已經遍歷完畢
            */
            
            //nextTable中的桶遍歷完畢
            while ((s = stack) != null && (index += (len = s.length)) >= n) {
                //彈出tab,獲取tab的遍歷狀態,開始遍歷tab中的桶
                n = len;
                index = s.index;
                tab = s.tab;
                s.tab = null;
                TableStack<K,V> next = s.next;
                s.next = spare; // save for reuse
                stack = next;
                spare = s;
            }
            if (s == null && (index += baseSize) >= n)
                index = ++baseIndex;
        }
        
             
        
        
  }
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容