圖解遍歷過程
說明:下文中的tab表示源table。nextTable表示擴容時,遷移的目標table
1 當遍歷到fwd節點的時候,說明正在擴容,此節點的數據已經遷移到了nextTable
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)中
3 將tab2的遍歷狀態push到stack,跳到nextTable(tab3)中繼續遍歷
(1) 按照擴容遷移規則,先遍歷index=2的hash桶
(2)然后遍歷index=6的hash桶(index+tab2.length=2+4=6)
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所指向的節點
5 遍歷nextTable(tab3)中的桶4(index=4)和桶8(index+tab2.length)
6 完成了nextTable(tab3)的遍歷,彈出tab2的遍歷狀態節點。
(1) nextTable(tab2)遍歷完畢。
7 彈出tab(tab1), 開始繼續遍歷tab1
(1) stack棧空了,而且spare棧按照出棧的順序保存了相關節點
(2) 遍歷完畢。
相關源碼分析
ConcurrentHashMap的遍歷操作,主要是通過如下迭代器實現: KeyIterator,EntryIterator,ValueIterator
類圖如下:
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;
}
}