pipeline
爲了更好地對集合進行並行操作,java8中加入了stream API。以前對集合的操作策略由客戶端提供,有了stream API後,對集合的操作集成到了集合內部,客戶端只需要按需調用即可。stream API支持函數式編程,它對集合的操作一般分爲三個階段:
(1)source:即集合的開始狀態。
(2)intermedia operations :0個或者多箇中間階段,比如Stream.filter,Stream.map等,中間階段通常都是lazy,比如filter操作並不會馬上開始過濾,而是返回一個新的stream對象。直到遇到terminal operation時纔會真正地執行。
(3)terminal operation:一個終結操作,比如foreach,IntStream.sum。
以代碼爲例分析stream的執行過程:
public static void main(String[] args) {
List<String> words = asList("aboutjava", "accessibility", "addressing", "addshortcut", "about", "all", "become","bacteriumparatyphosum");
// 以a開頭的字符串的最大長度
int result = words.stream().filter(s -> s.startsWith("a")).mapToInt(s -> s.length()).max().getAsInt();
System.out.println(result);
}
以上代碼是計算字符串列表中以a開頭的字符串的最大長度,一共包括兩個intermedia operations:filter和mapToInt,一個terminal operation:max。
如下圖所示,每一個操作都會創建一個Stream Pipeline,每個Pipeline包含一個Sink(也就是具體操作),max實際上是由reduce操作來實現的,Stream Pipeline通過upstream字段形成一個鏈表。
當調用max()方法時,PipelineHelper.wrapAndCopyInto()方法會被調用,這個方法的實現如下:
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
return sink;
}
主要做了兩件事:
1、將所有的Sink(Ops)進行合併,合併的代碼如下:
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
Objects.requireNonNull(sink);
for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink<P_IN>) sink;
}
這些Sink最終會通過downstream形成一個鏈表。
舉個栗子,要從豆腐中過濾出豆漿,通常的做法是準備好幾張過濾網(每張過濾網的功能不一樣,有的是過濾豆腐渣的,有的可能是過濾沙,有的過濾其他更小的雜質),將豆腐倒入第一個網中,用桶接住,將桶中的豆腐再倒入第二張網中。。。依次類推,最後得到豆漿。
這裏合併Sink的過程,就是把所有過濾網疊在一起,看起來像一張網但兼有以上幾張網的功能,以後只需要過濾一次就可以得到豆漿了。
2、遍歷集合,代碼如下:
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
wrappedSink.begin(spliterator.getExactSizeIfKnown());
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();
}
else {
copyIntoWithCancel(wrappedSink, spliterator);
}
wrappedSink.begin會依次調用所有Sink的begin方法,相當於疊好網,放到指定位置。
spliterator.forEachRemaining會依賴調用Sink的accept方法,相當於把豆腐倒在網上進行過濾。
這裏有對Short_circuit類型的操作進行特殊處理,比如findFirst只需要找一個滿足要求的,當找到一個後,就不再找了。好比桶中裝滿一杯豆漿後,就不倒豆腐了。
wrappedSink.end表明已處理完,直接出結果。
狀態
short-circuiting
final <P_IN> void copyIntoWithCancel(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
@SuppressWarnings({"rawtypes","unchecked"})
AbstractPipeline p = AbstractPipeline.this;
while (p.depth > 0) {
p = p.previousStage;
}
wrappedSink.begin(spliterator.getExactSizeIfKnown());
p.forEachWithCancel(spliterator, wrappedSink);
wrappedSink.end();
}
其中forEachWithCancel方法會跳過後面所有的Sink:final void forEachWithCancel(Spliterator<Integer> spliterator, Sink<Integer> sink) {
Spliterator.OfInt spl = adapt(spliterator);
IntConsumer adaptedSink = adapt(sink);
do { } while (!sink.cancellationRequested() && spl.tryAdvance(adaptedSink));
}
順序
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
list.stream().parallel().forEach(x -> System.out.print(x));
list.stream().parallel().forEachOrdered(x -> System.out.print(x));
}
除了使用forEachOrdered保證順序外,Collectors.toList()也可以保證順序,二都最終都是通過ForEachOrderedTask類來實現的,具體可以參看ForEachOp.ForEachOrderedTask類中的代碼。