從最早Java提供順序遍歷叠代器Iterator時,那個時候還是單核時代,但現在多核時代下,順序遍歷已經不能滿足需求了...如何把多個任務分配到不同核上並行執行,才是能最大發揮多核的能力,所以Spliterator應運而生啦
因為對於數據源而言...集合是描述它最多的情況,所以Java已經默認在集合框架中為所有的數據結構提供了壹個默認的Spliterator實現,相應的這個實現其實就是底層Stream如何並行遍歷(Stream.isParallel())的實現啦,因此平常用到Spliterator的情況是不多的...因為Java8這次正是壹次引用函數式編程的思想,妳只需要告訴JDK妳要做什麽並行任務,關註業務本身,至於如何並行,怎麽並行效率最高,就交給JDK自己去思考和優化速度了(想想以前寫如何並發的代碼被支配的恐懼吧)作為調用者我們只需要去關心壹些filter,map,collect等業務操作即可
所以想要看Spliterator的實現,可以直接去看JDK對於集合框架的實現,很多實現類妳可以在Spliterators中找到的,也可以直接去妳對應集合的stream方法中找到,比如ArrayList點進去的是Collection的默認實現,只需要提供壹個Spliterator的實現,然後用StreamSupport就可以構造壹個Stream了,相當方便
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
@Override
default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
對於Spliterator接口的設計思想,應該要提到的是Java7的Fork/Join(分支/合並)框架,總得來說就是用遞歸的方式把並行的任務拆分成更小的子任務,然後把每個子任務的結果合並起來生成整體結果。帶著這個理解來看看Spliterator接口提供的方法
boolean tryAdvance(Consumer<? super T> action);
Spliterator<T> trySplit();
long estimateSize();
int characteristics();
第壹個方法tryAdvance就是順序處理每個元素,類似Iterator,如果還有元素要處理,則返回true,否則返回false
第二個方法trySplit,這就是為Spliterator專門設計的方法,區分與普通的Iterator,該方法會把當前元素劃分壹部分出去創建壹個新的Spliterator作為返回,兩個Spliterator變會並行執行,如果元素個數小到無法劃分則返回null
第三個方法estimateSize,該方法用於估算還剩下多少個元素需要遍歷
第四個方法characteristics,其實就是表示該Spliterator有哪些特性,用於可以更好控制和優化Spliterator的使用,具體屬性妳可以隨便百度到,這裏就不再贅言
理解了接口方法的意思,現在再來看看自己的實現吧,由於大多數集合都被官方實現了,所以不能搞集合了,只有搞搞類似集合但又不是集合的東西...舉以下這個例子,例子反正感覺不是很好,只能說幫助理解哈Spliterator接口了:
問題:求這個字符串"12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd"中所有的數字之和例子:比如這種"12%sdf3",和就是12+3=15,這種"12%3 21sdas"和就是12+3+21=36
思路:字符串要用到Stream,只有把整個字符串拆分成壹個個Character,而是否是並行,按道理講只需要改壹個標誌位即可
先順序執行代碼方式:
/**
* 字符串中的數字計算器實現
*/
public class NumCounter {
private int num;
private int sum;
// 是否當前是個完整的數字
private boolean isWholeNum;
public NumCounter(int num, int sum, boolean isWholeNum) {
this.num = num;
this.sum = sum;
this.isWholeNum = isWholeNum;
}
public NumCounter accumulate(Character c){
if (Character.isDigit(c)){
return isWholeNum ? new NumCounter(Integer.parseInt("" + c), sum + num, false) : new NumCounter(Integer.parseInt("" + num + c), sum, false);
}else {
return new NumCounter(0, sum + num, true);
}
}
public NumCounter combine(NumCounter numCounter){
return new NumCounter(numCounter.num, this.getSum() + numCounter.getSum(), numCounter.isWholeNum);
}
public int getSum() {
return sum + num;
}
}
/**
* 測試類
*/
public class NumCounterTest {
public static void main(String[] args) {
String arr = "12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
System.out.println("ordered total: " + countNum(stream));
}
private static int countNum(Stream<Character> stream){
NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
return numCounter.getSum();
}
}
執行結果如下:
如果按照普通方式直接把stream改為並行流...執行結果明顯有點...不對
/**
* 測試類
*/
public class NumCounterTest {
public static void main(String[] args) {
String arr = "12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
// 調用parallel()變成並行流
System.out.println("ordered total: " + countNum(stream.parallel()));
}
private static int countNum(Stream<Character> stream){
NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
return numCounter.getSum();
}
}
此時錯誤的並行執行結果如下:
為什麽會執行錯誤,是因為默認的Spliterator在並行時並不知道整個字符串從哪裏開始切割,由於切割錯誤,導致把本來完整的數字比如123,可能就切成了12和3,這樣加起來的數字肯定不對
若是理解了上訴順序執行的NumCounter的邏輯,再來看看Spliterator的實現
/**
* 字符串中的數字分割叠代計算器實現
*/
public class NumCounterSpliterator implements Spliterator<Character> {
private String str;
private int currentChar = 0;
public NumCounterSpliterator(String str) {
this.str = str;
}
@Override
public boolean tryAdvance(Consumer<? super Character> action) {
action.accept(str.charAt(currentChar++));
return currentChar < str.length();
}
@Override
public Spliterator<Character> trySplit() {
int currentSize = str.length() - currentChar;
if (currentSize < 10) return null;
for (int pos = currentSize/2 + currentSize; pos < str.length(); pos++){
if (pos+1 < str.length()){
// 當前Character是數字,且下壹個Character不是數字,才需要劃分壹個新的Spliterator
if (Character.isDigit(str.charAt(pos)) && !Character.isDigit(str.charAt(pos+1))){
Spliterator<Character> spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
currentChar = pos;
return spliterator;
}
}else {
if (Character.isDigit(str.charAt(pos))){
Spliterator<Character> spliterator = new NumCounterSpliterator(str.substring(currentChar, pos));
currentChar = pos;
return spliterator;
}
}
}
return null;
}
@Override
public long estimateSize() {
return str.length() - currentChar;
}
@Override
public int characteristics() {
return ORDERED + SIZED + SUBSIZED + NONNULL + IMMUTABLE;
}
}
/**
* 測試類
*/
public class NumCounterTest {
public static void main(String[] args) {
String arr = "12%3 21sdas s34d dfsdz45 R3 jo34 sjkf8 3$1P 213ikflsd fdg55 kfd";
Stream<Character> stream = IntStream.range(0, arr.length()).mapToObj(arr::charAt);
System.out.println("ordered total: " + countNum(stream));
Spliterator<Character> spliterator = new NumCounterSpliterator(arr);
// 傳入true表示是並行流
Stream<Character> parallelStream = StreamSupport.stream(spliterator, true);
System.out.println("parallel total: " + countNum(parallelStream));
}
private static int countNum(Stream<Character> stream){
NumCounter numCounter = stream.reduce(new NumCounter(0, 0, false), NumCounter::accumulate, NumCounter::combine);
return numCounter.getSum();
}
}
這下可以看到執行結果是正確的了