Stream流
1. 操作类型
Stream API中的操作类型如下图所示 在图中可以看出,操作类型大概范围两类,每个大类中有两个子项
1. 中间操作:中间操作只是一种标记,只有结束操作才会触发实际计算
- 无状态:指元素的处理不受前面元素的影响;
- 有状态:有状态的中间操作必须等到所有元素处理之后才知道最终结果,比如排序是有状态操作,在读取所有元素之前并不能确定排序结果。
2.终止操作:顾名思义,就是得出最后计算结果的操作
- 短路操作:指不用处理全部元素就可以返回结果;
- 非短路操作:指必须处理所有元素才能得到最终结果。
2. Stream的操作过程
首先 准备了一些示例代码
public class TestStream {
public TestStream testMap() {
System.out.println("测试map方法");
return this; }
public boolean testBool() {
System.out.println("测试boolean方法");
return false; }
public boolean testTrue() {
System.out.println("测试 boolean true方法");
return true; }
public static void main(String[] args) {
List<TestStream> testStreams= Lists.newArrayList(new TestStream(),new TestStream(),new TestStream());
Stream<TestStream> result = testStreams
.stream()
.map(TestStream::testMap)
.filter(TestStream::testBool)
.filter(TestStream::testTrue)
.sorted();
}
}
我们准备了一个TestStream类,在该类中 准备一些测试lambda函数的一些方法。在main方法中,执行了一个相关的流操作,在控制台中可以看出。没有任何输出,说明Stream并没有真正执行到对应的方法中,因为我们没有写入终止操作,由此可以看出,在终止操作之前,Stream并没有真正的去执行每个中间操作,而是将中间操作记录了下来,在执行终止操作这一行的时候再去执行中间操作。那么具体的记录过程与执行过程是怎么样的呢
2.1 记录过程
点进源码跟入Steam方法,可以看到Collection的Stream方法调用的StreamSupport.stream()方法如下在该方法中,返回了一个ReferencePipeline.Head对象,看命名不难发现,这是记录管道操作的一个头节点对象,这个Head对象也是继承了ReferencePipeline对象的,所以后续的map ,filter等方法实际都是ReferencePipeline对象的方法,在构造方法中也是调用了父类AbstractPipeline类的构造方法,如下
AbstractPipeline(Spliterator<?> source,
int sourceFlags, boolean parallel) {
//上一个步骤
this.previousStage = null;
//源数据
this.sourceSpliterator = source;
//头步骤
this.sourceStage = this;
this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK; StreamOpFlag.INITIAL_OPS_VALUE);
this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
//执行深度
this.depth = 0;
//是否异步
this.parallel = parallel;
}
在Stream中的每一步操作都被定义为一个Stage 这里可以看到在构造方法中定义了一个previousStage 和 sourceStage 即上一个节点与头节点 在类中还有一个nextStage对象 如下图
可以看出stream实际上是构建了一个双向链表来记录每一步操作,那么我们继续看一下
list.map()方法
@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
//将当前节点作为参数传入作为上一个节点
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
//记住这个 onWrapSink!
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
//构造方法
AbstractPipeline(AbstractPipeline<?, E_IN, ?> previousStage, int opFlags) {
if (previousStage.linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
previousStage.linkedOrConsumed = true;
//指定上一个Stage的下一步为当前节点
previousStage.nextStage = this;
this.previousStage = previousStage;
this.sourceOrOpFlags = opFlags & StreamOpFlag.OP_MASK;
this.combinedFlags = StreamOpFlag.combineOpFlags(opFlags, previousStage.combinedFlags);
this.sourceStage = previousStage.sourceStage;
if (opIsStateful())
sourceStage.sourceAnyStateful = true;
this.depth = previousStage.depth + 1;
}
在该方法中,创建了一个StatelessOp对象,这个对象代表无状态的中间操作,该对象同样继承了ReferencePipeline,在该对象的构造方法中,可以看到讲调用该初始化方法的节点定义为上一个节点,并且对应的深度depth 也进行了+1操作。
我们总结一下, stream()方法得到的是HeadStage,之后每一个操作(Operation)都会创建一个新的Stage并以双向链表的形式结合在一起,每个Stage都记录了本身的操作,Stream就以此方式,实现了对操作的记录,注意结束操作不算depth的深度,它也不属于stage,但是我们的示例语句中没有写结束操作的代码,所以在这里提一下 Stream的Lazy机制 当该语句执行完的时候,我们在debug的过程中,并没有发现它进行执行任何map或者filter的逻辑,list也没有被改变,这就是Stream的Lazy机制。它的特点是:Stream直到调用终止操作时才会开始计算,没有终止操作的Stream将是一个静默的无操作指令
Stage相关类如下
2.2 执行过程
在了解执行过程之前,我们应该先了解另一个接口Sink,该接口继承了Consumer接口,在调用map, filter等无状态操作中返回的StatelessOp对象中,覆盖了opWrapSink方法,返回了一个Sink对象,并且将参数中的Sink对象作为构造方法中的参数传入了进去,走进构造方法后,可以看到
public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
}
在该对象中定义了一个downstream,该对象也是一个Sink类型的对象,并且在定义Sink对象时,覆盖了Consumer接口中的accept方法 如下
@Override
@SuppressWarnings("unchecked")
public final <R> Stream<R> map(Function<? super P_OUT, ? extends R> mapper) {
Objects.requireNonNull(mapper);
//将当前节点作为参数传入作为上一个节点
return new StatelessOp<P_OUT, R>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SORTED | StreamOpFlag.NOT_DISTINCT) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<R> sink) {
return new Sink.ChainedReference<P_OUT, R>(sink) {
@Override
public void accept(P_OUT u) {
downstream.accept(mapper.apply(u));
}
};
}
};
}
不难看出在执行accept方法时,就是将当前节点的操作结果,传入给downstream继续执行,而这个downstream则是通过onWrapSink方法中传入过来的。
了解了以上这些概念,我们可以走进结束操作,.collect(Collectors.toList());方法,在该方法中通过Collectors定一个另一个ArrayList收集器。并且传入了collect方法中,在该方法中
public final <R, A> R collect(Collector<? super P_OUT, A, R> collector) {
A container;
if (isParallel()
&& (collector.characteristics().contains(Collector.Characteristics.CONCURRENT))
&& (!isOrdered() || collector.characteristics().contains(Collector.Characteristics.UNORDERED))) {
container = collector.supplier().get();
BiConsumer<A, ? super P_OUT> accumulator = collector.accumulator();
forEach(u -> accumulator.accept(container, u));
}
else {
//非并行走这里。
container = evaluate(ReduceOps.makeRef(collector));
}
return collector.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)
? (R) container
: collector.finisher().apply(container);
}
我们暂时只看非并行的部分。在这一行通过ReduceOps定义了一个ReduceOp对象如下
public static <T, I> TerminalOp<T, I>
makeRef(Collector<? super T, I, ?> collector) {
Supplier<I> supplier = Objects.requireNonNull(collector).supplier();
BiConsumer<I, ? super T> accumulator = collector.accumulator();
BinaryOperator<I> combiner = collector.combiner();
class ReducingSink extends Box<I>
implements AccumulatingSink<T, I, ReducingSink> {
@Override
public void begin(long size) {
state = supplier.get();
}
@Override
public void accept(T t) {
accumulator.accept(state, t);
}
@Override
public void combine(ReducingSink other) {
state = combiner.apply(state, other.state);
}
}
return new ReduceOp<T, I, ReducingSink>(StreamShape.REFERENCE) {
@Override
public ReducingSink makeSink() {
return new ReducingSink();
}
@Override
public int getOpFlags() {
return collector.characteristics().contains(Collector.Characteristics.UNORDERED)
? StreamOpFlag.NOT_ORDERED
: 0;
}
};
}
public static <T>
Collector<T, ?, List<T>> toList() {
return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
(left, right) -> { left.addAll(right); return left; },
CH_ID);
}
CollectorImpl(Supplier<A> supplier,
BiConsumer<A, T> accumulator,
BinaryOperator<A> combiner,
Set<Characteristics> characteristics) {
this(supplier, accumulator, combiner, castingIdentity(), characteristics);
}
在makeRef方法中,返回了一个ReduceOp对象,该对象覆盖了makeSink()方法,返回了一个ReducingSink对象。我们继续往下走 ,走进evaluate 方法中
final <R> R evaluate(TerminalOp<E_OUT, R> terminalOp) {
assert getOutputShape() == terminalOp.inputShape();
if (linkedOrConsumed)
throw new IllegalStateException(MSG_STREAM_LINKED);
linkedOrConsumed = true;
return isParallel()
//并行执行
? terminalOp.evaluateParallel(this, sourceSpliterator(terminalOp.getOpFlags()))
//非并行执行
: terminalOp.evaluateSequential(this, sourceSpliterator(terminalOp.getOpFlags()));
}
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
//makeSink返回ReducingSink对象
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}
@Override
final <P_IN, S extends Sink<E_OUT>> S wrapAndCopyInto(S sink, Spliterator<P_IN> spliterator) {
copyInto(wrapSink(Objects.requireNonNull(sink)), spliterator);
return sink;
}
@Override
@SuppressWarnings("unchecked")
final <P_IN> Sink<P_IN> wrapSink(Sink<E_OUT> sink) {
Objects.requireNonNull(sink);
//第一次进入的sink为ReducingSink对象
for ( @SuppressWarnings("rawtypes") AbstractPipeline p=AbstractPipeline.this; p.depth > 0; p=p.previousStage) {
//当前这个p位调用collect方法之前的Stream对象
//在中间操作方法中,覆盖了onWrapSink方法,并且将传入的sink 定义成自己的下一个sink
sink = p.opWrapSink(p.previousStage.combinedFlags, sink);
}
return (Sink<P_IN>) sink;
}
可以看出 wrapsink方法中,是查找链表的头节点,并且调用每个节点的onWrapSink方法,在该方法中 传入当前节点的sink对象,并且将传入的对象定义成自己的下游,形成一个,从头节点到尾部节点的Sink单向链表如图所示
在wrapSink中,通过一层层的前置包装,返回头节点的Sink类传入copyInto方法中 如下
final <P_IN> void copyInto(Sink<P_IN> wrappedSink, Spliterator<P_IN> spliterator) {
Objects.requireNonNull(wrappedSink);
if (!StreamOpFlag.SHORT_CIRCUIT.isKnown(getStreamAndOpFlags())) {
//不是短路操作
wrappedSink.begin(spliterator.getExactSizeIfKnown());
spliterator.forEachRemaining(wrappedSink);
wrappedSink.end();
}
else {
//短路操作
copyIntoWithCancel(wrappedSink, spliterator);
}
}
在该方法中,先调用了wrappedSink.begin()方法 该方法默认实现为调用downstream的begin方法。相当于触发全部Sink的begin方法,做好运行前的准备。
具体循环的执行则是在spliterator.forEachRemaining(wrappedSink);方法中,操作如下
public void forEachRemaining(Consumer<? super E> action) {
int i, hi, mc; // hoist accesses and checks from loop
ArrayList<E> lst; Object[] a;
if (action == null)
throw new NullPointerException();
if ((lst = list) != null && (a = lst.elementData) != null) {
if ((hi = fence) < 0) {
mc = lst.modCount;
hi = lst.size;
}
else
mc = expectedModCount;
if ((i = index) >= 0 && (index = hi) <= a.length) {
for (; i < hi; ++i) {
@SuppressWarnings("unchecked") E e = (E) a[i];
//执行accept
action.accept(e);
}
if (lst.modCount == mc)
return;
}
}
throw new ConcurrentModificationException();
}
在forEachRemaining方法中,调用了accept方法 也就是在定义onWrapSink方法中 初始化Sink对象后定义的accept方法,将自己的执行结果传入downstream继续执行,也就是说,在调用结束操作后才实际执行每个方法。 在实际执行过后,在执行end方法进行结束操作,stream整体的流操作大概 就是如此。了解了大概远离后可以找一些常用的case来分析一下
2.3 具体分析
- 无状态操作,大部分无状态操作没有什么特殊处理,均是采用默认的begin end操作 修改部分accept动作,只是做一些数据的过滤以及修改操作
- 有状态操作: sorted() 看源码可以发现,在sorted方法中返回了一个OfRef对象,该对象继承StateFulOp属于一个有状态的操作,在该类中实现了onWrapSink方法如下
public Sink<T> opWrapSink(int flags, Sink<T> sink) {
Objects.requireNonNull(sink);
// If the input is already naturally sorted and this operation
// also naturally sorted then this is a no-op
if (StreamOpFlag.SORTED.isKnown(flags) && isNaturalSort)
//如果已经排序过了,就不在排序了
return sink;
else if (StreamOpFlag.SIZED.isKnown(flags))
//如果数量是已知的则使用数组方式
return new SizedRefSortingSink<>(sink, comparator);
//数量未知选择arraylist
else return new RefSortingSink<>(sink, comparator);
}
一般情况下都会选择list作为排序容器,大部分情况下都是不知道容器大小的,于是采用RefSortingSink类作为当前节点处理类,该类代码如下
private static final class RefSortingSink<T> extends AbstractRefSortingSink<T> {
private ArrayList<T> list;
RefSortingSink(Sink<? super T> sink, Comparator<? super T> comparator) {
super(sink, comparator);
}
@Override
public void begin(long size) {
if (size >= Nodes.MAX_ARRAY_SIZE)
throw new IllegalArgumentException(Nodes.BAD_SIZE);
list = (size >= 0) ? new ArrayList<T>((int) size) : new ArrayList<T>();
}
@Override
public void end() {
list.sort(comparator);
downstream.begin(list.size());
if (!cancellationRequestedCalled) {
list.forEach(downstream::accept);
}
else {
for (T t : list) {
if (downstream.cancellationRequested()) break;
downstream.accept(t);
}
}
downstream.end();
list = null;
}
@Override
public void accept(T t) {
list.add(t);
}
}
可以看到该Sink中的accept方法中,并没有执行下游的accept方法,而是将所有的数据装入了一个ArrayList,在end方法利用arrayList进行排序,并且继续开启后续的循环操作
3. 代码建议
- 短路操作不一定会执行所有元素
- sort方法一定将sort方法前的方法全部执行完毕后在执行后续的方法,所以sort方法 尽量在所有filter操作执行完成后 在进行sort