6.5 HashMap 內部類分割迭代器 HashMapSpliterator、KeySpliterator、ValueSpliterat、EntrySplitera
阿新 • • 發佈:2018-12-13
首先說說作用,原來的 HashMap 已經有了迭代器了,為什麼還要這些迭代器?難道是愛嗎?是責任嗎?(づ。◕‿‿◕。)づ
哈哈哈,李白說過,存在即合理
為了適應時代的變化,現在平行計算越來越需要,這個迭代器的誕生也是為了並行迭代而出現的,可以在多執行緒的情況下迭代同一個 HashMap,但是官方建議一個執行緒只和一個迭代器配合使用啦
好,入正題,在這裡 KeySpliterator、ValueSpliterat、EntrySplitera 都是繼承於 HashMapSpliterator,這裡只對 KeySpliterator 和 HashMapSpliterator 的原始碼解析,另外兩個原理都一樣,幾題請看官們看下面哎~
static class HashMapSpliterator<K,V> {
// 需要遍歷的 HashMap 物件
final HashMap<K,V> map;
// 當前正在遍歷的節點
Node<K,V> current; // current node
// 當前迭代器開始遍歷的桶索引
int index; // current index, modified on advance/split
// 當前迭代器遍歷上限的桶索引
int fence; // one past last index
// 需要遍歷的元素個數,暫時沒有發現用處比較大的地方
int est; // size estimate
// 期望運算元,用於多執行緒情況下,如果多個執行緒同時對 HashMap 進行讀寫,
// 那麼這個期望運算元 expectedModCount 和 HashMap 的 modCount 就會不一致,這時候拋個異常出來,稱為“快速失敗”
int expectedModCount; // for comodification checks
// 初始化
HashMapSpliterator(HashMap< K,V> m, int origin, int fence, int est, int expectedModCount) {
this.map = m;
this.index = origin;
this.fence = fence;
this.est = est;
this.expectedModCount = expectedModCount;
}
// 獲取柵欄?不不不,這個方法的作用是獲取一個當前迭代器的一個迭代範圍,例如返回的值是 4,那麼遍歷到第四個桶就會結束
// 如果 table 有資料的話,貌似返回的值永遠都是一樣的
final int getFence() { // initialize fence and size on first use
int hi;
// 第一個分割迭代器會執行下面 if 內的程式碼
if ((hi = fence) < 0) {
HashMap<K,V> m = map;
est = m.size;
expectedModCount = m.modCount;
Node<K,V>[] tab = m.table;
hi = fence = (tab == null) ? 0 : tab.length;
}
return hi;
}
// 獲取當前迭代器需要遍歷的元素個數
public final long estimateSize() {
getFence(); // force init
return (long) est;
}
}
static final class KeySpliterator<K,V> extends HashMapSpliterator<K,V> implements Spliterator<K> {
KeySpliterator(HashMap<K,V> m, int origin, int fence, int est, int expectedModCount) {
super(m, origin, fence, est, expectedModCount);
}
// 對當前迭代器進行分割
public KeySpliterator<K,V> trySplit() {
// 這裡的分割方法只是把當前迭代器的開始索引和最後索引除以二而已
int hi = getFence(), lo = index, mid = (lo + hi) >>> 1;
// 需要遍歷的元素個數 est 也需要除以二喇~
return (lo >= mid || current != null) ? null :
new KeySpliterator<>(map, lo, index = mid, est >>>= 1, expectedModCount);
}
// 在當前迭代器遍歷範圍遍歷一遍
public void forEachRemaining(Consumer<? super K> action) {
int i, hi, mc;
if (action == null)
throw new NullPointerException();
HashMap<K,V> m = map;
Node<K,V>[] tab = m.table;
if ((hi = fence) < 0) {
mc = expectedModCount = m.modCount;
hi = fence = (tab == null) ? 0 : tab.length;
}
else
mc = expectedModCount;
if (tab != null && tab.length >= hi &&
(i = index) >= 0 && (i < (index = hi) || current != null)) {
Node<K,V> p = current;
current = null;
do {
if (p == null)
p = tab[i++];
else {
action.accept(p.key);
p = p.next;
}
} while (p != null || i < hi);
if (m.modCount != mc)
throw new ConcurrentModificationException();
}
}
// 會遍歷迭代器遍歷的範圍之內的元素,當找到第一個非空元素的時候就會停止遍歷
public boolean tryAdvance(Consumer<? super K> action) {
int hi;
if (action == null)
throw new NullPointerException();
Node<K,V>[] tab = map.table;
if (tab != null && tab.length >= (hi = getFence()) && index >= 0) {
while (current != null || index < hi) {
if (current == null)
current = tab[index++];
else {
K k = current.key;
current = current.next;
action.accept(k);
if (map.modCount != expectedModCount)
throw new ConcurrentModificationException();
return true;
}
}
}
return false;
}
// 技術有限,暫時還不清楚有啥用,網上一大片說什麼特徵碼什麼的,但是沒有發現哪裡有用到,
// 難道做拓展是要用到?再底下的一層遍歷的時候要用到?有點不明不白,不甘心哎~
public int characteristics() {
return (fence < 0 || est == map.size ? Spliterator.SIZED : 0) | Spliterator.DISTINCT;
}
}
測試程式碼
static class MyThread implements Runnable {
Spliterator<Integer> spliterator;
String threadName;
MyThread(Spliterator<Integer> spliterator, String threadName) {
this.spliterator = spliterator;
this.threadName = threadName;
}
@Override
public void run() {
spliterator.forEachRemaining(s -> {
System.out.println(threadName + "=" + s);
});
}
}
public static void main(String[] args) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < 23; i++) {
map.put(i, i);
}
Spliterator<Integer> s1 = map.keySet().spliterator();
Spliterator<Integer> s2 = s1.trySplit();
Spliterator<Integer> s3 = s2.trySplit();
Thread t1 = new Thread(new MyThread(s1, "執行緒1"));
Thread t2 = new Thread(new MyThread(s2, "執行緒2"));
Thread t3 = new Thread(new MyThread(s3, "執行緒3"));
t1.start();
t2.start();
t3.start();
}
輸出結果
執行緒2=8 執行緒2=9 執行緒2=10 執行緒2=11 執行緒2=12 執行緒2=13 執行緒2=14 執行緒1=16 執行緒2=15 執行緒3=0 執行緒1=17 執行緒1=18 執行緒1=19 執行緒1=20 執行緒1=21 執行緒3=1 執行緒1=22 執行緒3=2 執行緒3=3 執行緒3=4 執行緒3=5 執行緒3=6 執行緒3=7