在JAVA8之前的传统编程方式,如果我们需要操作一个集合数据,需要使用集合提供的API,通过一个循环去获取集合的元素,这种访问数据的方式会使代码显得臃肿,JAVA8新引入的Stream类,用于重新封装集合数据,通过使用流式Stream代替常用集合数组、list和map的遍历操作可以极大的提高效率。

一、Stream的组成

数据源(Source) + 0个或多个中间操作(intermediate operation)和终止操作(terminal operation)

数据源:数据源头,可为数组、Collection、I/O资源和生成函数

二、构造流(Stream)

Arrays构造流

public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) {
return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false);
}

由源码可知Arrays类由传入的数组生成一个Stream是委托StreamSupport来构建的,下面看下StreamSupport的源码

public static <T> Stream<T> stream(Spliterator<T> spliterator, boolean parallel) {
Objects.requireNonNull(spliterator);
return new ReferencePipeline.Head<>(spliterator,
StreamOpFlag.fromCharacteristics(spliterator),
parallel);
}

StreamSupport首先对spliterator参数进行判空,然后把它作为参数通过调用ReferencePipeline的内部类Head的构造函数生成一个ReferencePipeline.Head实例返回,通过对Head类源码的阅读可知他是ReferencePipeline的一个内部子类

static class Head<E_IN, E_OUT> extends ReferencePipeline<E_IN, E_OUT> {

        Head(Spliterator<?> source,
int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
} } ReferencePipeline(Spliterator<?> source,
int sourceFlags, boolean parallel) {
super(source, sourceFlags, parallel);
}
AbstractPipeline(Spliterator<?> source,
int sourceFlags, boolean parallel) {
this.previousStage = null;
this.sourceSpliterator = source;
this.sourceStage = this;
this.sourceOrOpFlags = sourceFlags & StreamOpFlag.STREAM_MASK;
// The following is an optimization of:
// StreamOpFlag.combineOpFlags(sourceOrOpFlags, StreamOpFlag.INITIAL_OPS_VALUE);
this.combinedFlags = (~(sourceOrOpFlags << 1)) & StreamOpFlag.INITIAL_OPS_VALUE;
this.depth = 0;
this.parallel = parallel;
}

Head构造函数内部调用父类ReferencePipeline的构造函数,而ReferencePipeline的构造函数则调用父类AbstractPipeline构造函数最终完成Stream的构建,阅读源码我们可知构造流的核心是Spliterator,而最初持有他的是AbstractPipeline,现在我们来看下Spliterator接口

下面是源码对Spliterator的注释

/*
* An object for traversing and partitioning elements of a source. The source
* of elements covered by a Spliterator could be, for example, an array, a
* {@link Collection}, an IO channel, or a generator function.
**/

翻译过来的意思是该接口是用于对数据源进行遍历和分区,即Spliterator对象封装了对数据源和分区的能力,接口声明的方法如下:

tryAdvance - 单元素遍历

trySplit - 分区抽象

forEachRemaining - 批量遍历

Stream - 实现原理

estimateSize - 默认实现,返回估计的大小

getExactSizeIfKnown - 默认实现,返回元素集合的确切大小

characteristics - 默认实现,返回当前spliterator源数据的一组特征值

hasCharacteristics - 默认实现,是否源数据是否包含该特征值

getComparator - 默认实现,如果该Spliterator操作的数据源是有序的,那么返回他的Comparator

好了,现在我们基本清楚Spliterator的作用和他在流中的定位,现在让我们回过头看下Arrays根据数组构造Stream流的JDK源码,深入到AbstractPipeline我们可以看到,Stream构造流是通过委托StreamSupport实现的,而核心是构建一个ArraySpliterator对象,可见,构造流(Stream)的核心就是构造一个Spliterator

Collection构造流

由上述对Arrays构造流分析可知构建流的核心是Spliterator,我们直接查看JAVA8中Collection构造流源码

 default Spliterator<E> spliterator() {
return Spliterators.spliterator(this, 0);
}
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}

Collection容器构造流与Arrays一样也是委托StreamSupport,代码设计的好的一点是他的流构建是在接口层面实现的,通过把他自身作为参数传入返回一个返回了一个IteratorSpliterator对象,不同集合的返回的IteratorSpliterator对象的tryAdvance等遍历方法不同,具体到IteratorSpliterator源码如下

        @Override
public boolean tryAdvance(Consumer<? super T> action) {
if (action == null) throw new NullPointerException();
if (it == null) {
it = collection.iterator();
est = (long) collection.size();
}
if (it.hasNext()) {
action.accept(it.next());
return true;
}
return false;
}

可知IteratorSpliterator对源码的遍历处理是通过传入一个Consumer消费函数消费不同Collection实现类Iterator迭代器获取的元素实现,感谢JAVA8接口的default设计。

三、Stream常用方法列表

Stream中间操作方法列表

方法 参数 用途
concat Stream< ? extends T> a, Stream< ? extends T> b 将两个流合起来形成新流
distinct 将流里的元素按照Ojbect.equal方法进行聚合去重,返回一个去重结果的新流
empty 返回一个空的流
filter Predicate< ? super T> predicate 按照谓词参数predicate过滤,返回过滤后的流Stream
flatMap Function< ? super T, ? extends Stream< ? extends R>> mapper 将流里的元素T,按照参数Function进行处理,处理结果是一个子流Stream< ? extends R>,后续将子流flat打平,形成元素R的新流。类似的有flatToDouble、flatToInt和flatToLong
limit long maxSize 返回一个新流,只包含maxSize个元素,其他被truncate掉
map Function< ? super T, ? extends R> mapper 经典的map操作,对流里的每个元素,通过参数mapper映射为一个新的元素,返回新元素的流。类似map有mapToDouble、mapToInt和mapToLong
peek Consumer< ? super T> action 这个动作非常有趣,它并不改变流,而是对流里的每个元素执行一个Consumer,对其进行一次处理。原始流不变继续返回
skip long n 跳过n个元素,从第n+1个元素开始返回一个新的流
sorted Comparator< ? super T> comparator 根据参数排序器对流进行排序,返回新的流。如果参数为空,则按照自然序排

Stream终止操作方法列表

方法 参数 用途
allMatch Predicate< ? super T> predicate 根据谓词函数判断流里的元素是否都满足,返回对应的boolean值
anyMatch Predicate< ? super T> predicate 根据谓词函数判断流里的元素是否存在一个或多个满足,返回对应的boolean值
noneMatch Predicate< ? super T> predicate 根据谓词函数判断流里的元素是否不存在任何一个元素满足,返回对应的boolean值
count 返回这个流里元素的个数
findAny 返回一个Optional对象,这个等价于对于一个流执行一个select操作,返回一条记录
findFirst 返回这个流里的第一个元素的Optional,如果这个流不是有序的,则返回任意元素
forEach Consumer< ? super T> action 对这个流的每个元素,执行参数Consumer
forEachOrdered Consumer<? super T> action 针对forEach在并行流里对有序元素的输出不足,这个方法确保并行流中按照原来顺序处理
max Comparator<? super T> comparator 返回一个Optional值,包含了流里元素的max,条件是按照参数排序器排序
min Comparator<? super T> comparator 返回一个Optional值,包含了流里元素的min,条件是按照参数排序器排序
reduce BinaryOperator< T> accumulator 经典的reduce,就是根据一个二元操作算子,将流中的元素逐个累计操作一遍,初始元素以foundAny结果为主
reduce T identity, BinaryOperator< T> accumulator 与上面的方法一致,只不过多了一个初始值,不需要findAny了
reduce U identity,BiFunction< U, ? super T, U> accumulator,BinaryOperator< U> combiner 最复杂的reduce,看到combiner会不会有联想?它做的也是对于一个流里的元素T,使用二元函数accumulator计算,计算的值累计到U上,因为之前的reduce要求流元素和结果元素类型一致,所以有限制。而该reduce函数,支持T和U类型不同,通过二元函数转换,但是要求combiner会执行这个事情,要求“ combiner.apply(u, accumulator.apply(identity, t)) == accumulator.apply(u, t)”
collect Supplier< R> supplier,BiConsumer< R, ? super T> accumulator,BiConsumer< R, R> combiner 超级强大的方法。常规的reduce是返回不可变的值。而collect可以将reduce后的值升级为一个可变容器。具体这个方法就是对流里每个元素T,将Supplier提供的值R作为初始值,用BiConsumer的accumulator进行累加计算。combiner的作用和要求和reduce是一样的
collect Collector< ? super T, A, R> collector 和上面的collect一致,只不过Collector封装了一组上面的参数,T是流里的元素,A是累计中间结果,R是返回值的类型(collect的话就是容器了)

四、Stream工作原理

下面我们分析下Stream的工作原理

        Integer[] array = new Integer[]{1,2,3,4};
Optional<Integer> result = Stream.of(array).filter(v -> v > 2).sorted((v1, v2) -> v2.compareTo(v1)).limit(2).reduce((v1, v2) -> v1 + v2); System.out.println(result.get());

  首先调用Stream.of方法根据一个Integer对象数组构建了流,函数内部是通过调用Arrays.stream方法返回流,对应的Spliterator实现是ArraySpliterator,然后调用filter方法过滤,我们分析下这个阶段对应的源码

    @Override
public final Stream<P_OUT> filter(Predicate<? super P_OUT> predicate) {
Objects.requireNonNull(predicate);
return new StatelessOp<P_OUT, P_OUT>(this, StreamShape.REFERENCE,
StreamOpFlag.NOT_SIZED) {
@Override
Sink<P_OUT> opWrapSink(int flags, Sink<P_OUT> sink) {
return new Sink.ChainedReference<P_OUT, P_OUT>(sink) {
@Override
public void begin(long size) {
downstream.begin(-1);
} @Override
public void accept(P_OUT u) {
if (predicate.test(u))
downstream.accept(u);
}
};
}
};
}

由源码可知filter把前面构造的流(Stream)本身作为参数返回了一个StatelessOp实现对象,深入StatelessOp类我们知道它是ReferencePipeline的一个内部类,继承ReferencePipeline,而上面我们分析过ReferencePipeline继承自AbstractPipeline,回到filter源码,我们看到在filter方法里面实现了超类AbstractPipeline的OpWrapSink方法

    /**
* Accepts a {@code Sink} which will receive the results of this operation,
* and return a {@code Sink} which accepts elements of the input type of
* this operation and which performs the operation, passing the results to
* the provided {@code Sink}.
*
* @apiNote
* The implementation may use the {@code flags} parameter to optimize the
* sink wrapping. For example, if the input is already {@code DISTINCT},
* the implementation for the {@code Stream#distinct()} method could just
* return the sink it was passed.
*
* @param flags The combined stream and operation flags up to, but not
* including, this operation
* @param sink sink to which elements should be sent after processing
* @return a sink which accepts elements, perform the operation upon
* each element, and passes the results (if any) to the provided
* {@code Sink}.
*/
abstract Sink<E_IN> opWrapSink(int flags, Sink<E_OUT> sink);

ChainedReference.java源码

/**
* Abstract {@code Sink} implementation for creating chains of
* sinks. The {@code begin}, {@code end}, and
* {@code cancellationRequested} methods are wired to chain to the
* downstream {@code Sink}. This implementation takes a downstream
* {@code Sink} of unknown input shape and produces a {@code Sink<T>}. The
* implementation of the {@code accept()} method must call the correct
* {@code accept()} method on the downstream {@code Sink}.
*/
static abstract class ChainedReference<T, E_OUT> implements Sink<T> {
protected final Sink<? super E_OUT> downstream; public ChainedReference(Sink<? super E_OUT> downstream) {
this.downstream = Objects.requireNonNull(downstream);
} @Override
public void begin(long size) {
downstream.begin(size);
} @Override
public void end() {
downstream.end();
} @Override
public boolean cancellationRequested() {
return downstream.cancellationRequested();
}
}

通过对上述源码的分析可知Sink(Consumer的一个派生类)在流中的作用其实是用于控制流中间阶段的数据、大小等状态信息,在Sink方法中还定义了两个方法,begin和end,begin在Sink的accept方法之前调用,end在accept方法之后调用,主要是用于对流程数据进行一些额外的控制,现在我们在结合分析filter源码发现,为了维护支持流(Stream)的中间操作状态信息,JAVA8流在结构上其实被设计成一个链表结构,一个Head起始节点,多个中间节点StatelessMap(继承自ReferencePipeline),而流程中管理和控制数据状态信息的实际是其中的Sink。

  接下来是到sorted排序阶段,我们继续深入源码。

ReferencePipeline.java

@Override
public final Stream<P_OUT> sorted(Comparator<? super P_OUT> comparator) {
return SortedOps.makeRef(this, comparator);
}

SortedOps.java

    static <T> Stream<T> makeRef(AbstractPipeline<?, T, ?> upstream,
Comparator<? super T> comparator) {
return new OfRef<>(upstream, comparator);
}

排序是一个有状态的中间操作,与filter阶段类似sorted方法实际返回的是一个OfRef对象,深入SortedOps的makeRef方法,可知返回了一个OfRef实例分析该类可知该类是StatefulOp的子类,该类持有一个排序器,解读StatefulOp类源码可知该类是流中间状态的基类,对比StatelessOp类,接下来通过源码分析方法的逻辑

SortedOps.OfRef

        @Override
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);
else
return new RefSortingSink<>(sink, comparator);
}

分析代码可知实现逻辑与filter阶段类似,都是通过Sink控制流的数据和中间状态信息,流程逻辑是先对入参Sink判空,如果流有序,直接返回sink,否则判断是否有界,如果有界返回一个SizedRefSortingSink对象,否则返回一个RefSortingSink对象,深入两个类,不出意外,前者内部是通过数组保存数据,后者是通过一个ArrayList实例保存,两者均是在end方法里借由内部排序器完成元素排序

  接下来的limit截取阶段类似,读者可自行分析,大体的实现逻辑与上述两个阶段并无二致

  最后到了终止操作阶段reduce

ReferencePipeline.java

    @Override
public final Optional<P_OUT> reduce(BinaryOperator<P_OUT> accumulator) {
return evaluate(ReduceOps.makeRef(accumulator));
}

AbstractPipeline.java

    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()));
}

ReduceOps.java

        @Override
public <P_IN> R evaluateSequential(PipelineHelper<T> helper,
Spliterator<P_IN> spliterator) {
return helper.wrapAndCopyInto(makeSink(), spliterator).get();
}

AbstractPipeline.java

    @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
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);
}
}

通过对源码的分析可知,在Stream的中间阶段如果没有意外中断,代码执行到终止操作时才开始执行前面定义的各个中间操作,也就是说Stream的中间操作的执行方式都是lazy,读者可自行在中间流的opWrapSink打断点,去掉流的终止操作进行校验。

JAVA8之数据流Stream的更多相关文章

  1. 这可能是史上最好的 Java8 新特性 Stream 流教程

    本文翻译自 https://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ 作者: @Winterbe 欢迎关注个人微信公众 ...

  2. Java8 如何进行stream reduce,collection操作

    Java8 如何进行stream reduce,collection操作 2014-07-16 16:42 佚名 oschina 字号:T | T 在java8 JDK包含许多聚合操作(如平均值,总和 ...

  3. Java8 方式解决Stream流转其他数组

    Java8 方式解决Stream流转其他数组 一. 题记:原来的List转数组用的是如下方式: example private static void listToStringArray(List l ...

  4. Java8 新特性 Stream 非短路终端操作

    非短路终端操作 Java8 新特性 Stream 练习实例 非短路终端操作,就是所有的元素都遍厉完,直到最后才结束.用来收集成自己想要的数据. 方法有: 遍厉 forEach 归约 reduce 最大 ...

  5. Java8 新特性 Stream 短路终端操作

    短路终端操作 Java8 新特性 Stream 练习实例 传入一个谓词,返回传为boolean,如果符合条件,则直接结束流. 匹配所有 allMatch 任意匹配 anymMatch 不匹配 none ...

  6. Java8 新特性 Stream 无状态中间操作

    无状态中间操作 Java8 新特性 Stream 练习实例 中间无状态操作,可以在单个对单个的数据进行处理.比如:filter(过滤)一个元素的时候,也可以判断,比如map(映射)... 过滤 fil ...

  7. Java8 新特性 Stream() API

    新特性里面为什么要加入流Steam() 集合是Java中使用最多的API,几乎每一个Java程序都会制造和处理集合.集合对于很多程序都是必须的,但是如果一个集合进行,分组,排序,筛选,过滤...这些操 ...

  8. java8学习之Stream分组与分区详解

    Stream应用: 继续举例来操练Stream,对于下面这两个集合: 需求是:将这两个集合组合起来,形成对各自人员打招呼的结果,输出的结果如: "Hi zhangsan".&quo ...

  9. 【Java8新特性】面试官问我:Java8中创建Stream流有哪几种方式?

    写在前面 先说点题外话:不少读者工作几年后,仍然在使用Java7之前版本的方法,对于Java8版本的新特性,甚至是Java7的新特性几乎没有接触过.真心想对这些读者说:你真的需要了解下Java8甚至以 ...

随机推荐

  1. CF数据结构练习(二)

    1. 833D Red-Black Cobweb 大意: 给定树, 边为黑色或白色, 求所有黑白边比例在$[\frac{1}{2},2]$内的路径边权乘积的乘积. 考虑点分治, 记黑边数为$a$, 白 ...

  2. 剑指Offer-和为S的连续正数序列

    题目: 小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100.但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100(至少包括两个数).没多久,他就 ...

  3. loglog 函数的使用

    验证数值算法的正确性,有一个很重要的指标就是收敛阶($\tt\bf Convergent~Rate$) 当有误差估计: $$Error=\lVert u(x)-u_N(x) \rVert \simeq ...

  4. wpf 实现 css3 中 boxshadow inset 效果

    using System; using System.Linq; using System.Windows; using System.Windows.Controls; using System.W ...

  5. .net mvc 上传头像

    我用的是mvc5  开发环境vs2017 [仅供参考] [视图代码] <div > <img src="@path" alt="@att.Count&q ...

  6. zzw原创_cmd下带jar包运行提示 “错误: 找不到或无法加载主类 ”

    在windows下编译java,由于是临时测试一下文件,不想改classpath,就在命令行中用 -cp 或classpath引入jar包,用javac编译成功,便使用java带-cp 或classp ...

  7. Objective-C基础教程 笔记

    一.对C的扩展 1. #import VS #include C语言使用#include语句通知编译器应在头文件中查询定义. OC中也可以使用#include,但几乎不这么用,而是使用#import. ...

  8. log日志文件

    单文件写 根据日志的等级是否写入,下面的一个例子就是等级为10,大于等于等级10的记录,小于的话就不记录,在创建之前先进行基本的日志格式配置 import logging logging.basicC ...

  9. loj.ac:#10024. 「一本通 1.3 练习 3」质数方阵

    CSDN的博客 友键 题目描述 质数方阵是一个\(5×5\)的方阵,每行.每列.两条对角线上的数字可以看作是五位的素数.方格中的行按照从左到右的顺序组成一个素数,而列按照从上到下的顺序.两条对角线也是 ...

  10. java第五次笔记