由于好久好久没写文章了,也没啥时间,今天周末抽个时间写一篇关于Java8中的Stream的foreach描述
接口
void forEach(Consumer super T> action);
使用1:
Stream.generate(random).limit(10).forEach(System.out::println);//可传入方法
使用2:
roster.stream().parallel().filter(p1.negate()).forEach(p -> t.test(p));//也可以实现接口
这里引入了.parallel() 这是并行运行的标志,还有个sequential() 顺序执行,在归约操作之前,这两者可以切换使用,但是要慎重。Parallel()的使用考虑到流长度、分割最小单位等考虑,他并不是肯定比顺序流遍历快,而是看流的量,如果用到limit这些操作,不建议使用并行流,物极必反,并行流的代价就是消耗cpu,这中间的时间不可估计。
他有几个接口方法
boolean tryAdvance(Consumer super T> action);
Spliterator trySplit();
long estimateSize();
int characteristics();
与往常一样,T是Spliterator遍历的元素的类型。tryAdvance方法的行为类似于普通的 Iterator,因为它会按顺序一个一个使用Spliterator中的元素,并且如果还有其他元素要遍历就返回true。但trySplit是专为Spliterator接口设计的,因为它可以把一些元素划出去分给第二个Spliterator(由该方法返回),让它们两个并行处理。Spliterator还可通过 estimateSize方法估计还剩下多少元素要遍历,因为即使不那么确切,能快速算出来是一个值也有助于让拆分均匀一点。重要的是,要了解这个拆分过程在内部是如何执行的,以便在需要时能够掌控它。
这是他的实现类(部分),所以为什么LongStream、IntegerStream、DoubleStream、List等能直接forEach并可以并行遍历操作归功于Spliterator.
ReferencePipeline他是一个抽象类,继承AbstractPipeline和实现Stream类,可以理解为一个遍历的管道的抽象类。
ReferencePipeline有一个静态内部类Head,并且也继承了ReferencePipeline
我们不妨看看Heap相关的方法,首先我们看下他的构造器
Head(Spliterator> source,
int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
source:可分迭代器
parallel:是否并行操作
sourceFlags:输入源管道元素类型标志(this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;)forEach方法的实现
@Override
public void forEach(Consumer super E_OUT> action) {
if (!isParallel()) {
sourceStageSpliterator().forEachRemaining(action);
}
else {
super.forEach(action);
}
}
首先判断是否使用并行流,如果不是就是普通的forEach遍历,也就是通过forEachRemaining方法(顺序流)。
如果是并行流:
通过接口TerminalOp调用evaluateParallel方法,有四种实现REFERENCE, INT_VALUE, LONG_VALUE, DOUBLE_VALUE
看下ForEachOps中有个ForEachOp抽象类实现
@Override
public Void evaluateParallel(PipelineHelper helper,
Spliterator spliterator) {
if (ordered)
new ForEachOrderedTask<>(helper, spliterator, this).invoke();
else
new ForEachTask<>(helper, spliterator, helper.wrapSink(this)).invoke();
return null;
}
看到这里的时候已经很显然易见了,Stram分装的foeach最终的运行是用过jdk7引进fork/join框架。
由于这篇文章主要描述关于jdk8中Steam中foreach的原理,下次抽个时间再写一篇关于fork/join的运行原理。
Stream中的顺序遍历和并行遍历是可控的,比如说map操作是并行的,但是filter操作是顺序的,这个都可以的。还是一句话,不要盲目乱使用。
if (!isParallel()) {
sourceStageSpliterator().forEachRemaining(action);
}
else {
super.forEach(action);
}
每次遍历都会判断当前操作是并行还是串行。
今天先写到这里,找个时间再详细写写,把一些个人所觉得有意思的代码程序分享出来