在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. python-Excel读取-合并单元格读取

    python-Excel读取-合并单元格读取(后续会补充python-Excel写入的部分) 1. python读取Excel单元格 代码包含读取Excel中数据,以及出现横向合并单元格,以及竖向合并 ...

  2. rocketMQ No route info of this topic 错误

    最近在使用rocketmq 发送消息,出现了No route info of this topic 异常,但奇怪的是我的其它的服务都可以成功发送,唯有crs服务不能成功发送,在网上搜索的解决方式基本上 ...

  3. CF 552(div 3) E Two Teams 线段树,模拟链表

    题目链接:http://codeforces.com/contest/1154/problem/E 题意:两个人轮流取最大值与旁边k个数,问最后这所有的数分别被谁给取走了 分析:看这道题一点思路都没有 ...

  4. ArcGIS中KML转为shp文件

    问题:如何将KML转为shp文件? 方法: 1.打开ArcMap -> ArcToolbox: 2.在ArcToolbox中选择“转换工具”-> “由KML转出” -> “KML转图 ...

  5. vue keep-alive 取消某个页面缓存问题

    keep-alive keep-alive是Vue提供的一个抽象组件,用来对组件进行缓存,从而节省性能,由于是一个抽象组件,所以在v页面渲染完毕后不会被渲染成一个DOM元素 <keep-aliv ...

  6. SQL运行优化收藏

    如何让你的SQL运行得更快(转贴) ---- 人们在使用SQL时往往会陷入一个误区,即太关注于所得的结果是否正确,而忽略了不同的实现方法之间可能存在的性能差异,这种性能差异在大型的或是复杂的数据库环境 ...

  7. 从零开始搭建Webpack+react框架

    1.下载node.js Node.js官网下载 , 安装: 安装成功后在控制台输入node -v 可查看当前版本: $ node -v v10.15.0 输入npm -v查看npm版本: $ npm ...

  8. mysql 联合表(federated)及视图

    1)验证环境 源库:192.168.8.75 centos 7.5 mysql8.3 目标库:192.168.8.68 redhat 6.8 mysql5.7 2)登录源库并创建源表 $ mysql ...

  9. (1)vue点击图片预览(可旋转、翻转、缩放、上下切换、键盘操作)

    今天做项目的时候,遇到了新需求,需要把点击图片放大的功能.学习了一下GitHub上的viewerjs插件 GitHub地址:https://github.com/fengyuanchen/viewer ...

  10. Angular2+实现右键菜单的重定义--contextmenu

    在做需求时用到video这个html5的新增标签,然后公司要求把video的右键屏蔽了去,我在网上找了很久没找到完整的方法来实现这个功能,只能自己摸索着来. 不说废话,先上干货 0.0 video.c ...