本章中,你将学习到怎样使用Stream API进行开发。首先,你将会了解Stream API背后的机制,什么是流以及流的用处。其次,你将学习到一系列的流式操作、流式数据处理模型以及能让你写出更复杂数据查询的流式集合操作。接下来是如何应用流式操作的例子。最后,你将学习到并行流。
为什么需要流式操作
集合API是Java API中最重要的部分。基本上每一个java程序都离不开集合。尽管很重要,但是现有的集合处理在很多方面都无法满足需要。
一个原因是,许多其他的语言或者类库以声明的方式来处理特定的数据模型,比如SQL语言,你可以从表中查询,按条件过滤数据,并且以某种形式将数据分组,而不必需要了解查询是如何实现的——数据库帮你做所有的脏活。这样做的好处是你的代码很简洁。很遗憾,Java没有这种好东西,你需要用控制流程自己实现所有数据查询的底层的细节。
其次是你如何有效地处理包含大量数据的集合。理想情况下,为了加快处理过程,你会利用多核架构。但是并发程序不太好写,而且很容易出错。
Stream API很好的解决了这两个问题。它抽象出一种叫做流的东西让你以声明的方式处理数据,更重要的是,它还实现了多线程:帮你处理底层诸如线程、锁、条件变量、易变变量等等。
例如,假定你需要过滤出一沓发票找出哪些跟特定消费者相关的,以金额大小排列,再取出这些发票的ID。如果用Stream API,你很容易写出下面这种优雅的查询:
本章后面,你将了解到这些代码流程的细节。
什么是流
说了这么多,到底什么是流?通俗地讲,你可以认为是支持类似数据库操作的“花哨的迭代器”。技术上讲,它是从某个数据源获得的支持聚合操作的元素序列。下面着重介绍一下正式的定义:
元素序列
针对特定元素类型的有序集合流提供了一个接口。但是流不会存储元素,只会根据要求对其做计算。
数据源
流所用到的数据源来自集合、数组或者I/O。
聚合操作
流支持类似数据库的操作以及函数式语言的基本操作,比如filter,map,reduce,findFirst,allMatch,sorted等待。
此外,流操作还有两种额外的基础属性根据不同的集合区分:
管道连接
许多流操作返回流本身,这种操作可以串联成很长的管道,这种方式更加有利于像延迟加载,短路,循环合并等操作。
内部迭代器
不像集合依赖外部迭代器,流操作在内部帮你实现了迭代器。
流操作
流接口在java.util.stream.Stream定义了许多操作,这些可以分为以下两类:
- 像filter,sorted和map一样的可以被连接起来形成一个管道的操作。
- 像collect,findFirst和allMatch一样的终止管道并返回数据的操作。
Filter
有好几个方法可以用来从流里面过滤出元素:
filter
通过传递一个预期匹配的对象作为参数并返回一个包含所有匹配到的对象的流。
distinct
返回包含唯一元素的流(唯一性取决于元素相等的实现方式)。
limit
返回一个特定上限的流。
skip
返回一个丢弃前n个元素的流。
讯享网
Matching
匹配是一个判断是否匹配到给定属性的普遍的数据处理模式。你可以用anyMatch,allMatch和noneMatch来匹配数据,它们都需要一个预期匹配的对象作为参数并返回一个boolen型的数据。例如,你可以用allMatch来检查是否所有的发票流里面的元素的值都大于1000:
Finding
此外,流接口还提供了像findFirst和findAny等从流中取出任意的元素。它们能与像filter方法相连接。findFirst和findAny都返回一个可选对象(我们已经在第一章中讨论过)。
讯享网
Mapping
Reducing
reduce方法需要两个参数:
- 初始值,这里是0

- 一个BinaryOperator方法连接两个元素产生一个新元素。reduce方法本质上是抽象了重复方法模式。其他查询像“计算总和” 或者“计算最大值” 都是reduce方法的特殊用例,比如:
Collectors
目前为止你所了解的方法都是返回另一个流或者一个像boolean,int类型的值,或者返回一个可选对象。相比之下,collect方法是一个结束操作,它可以使流里面的所有元素聚集到汇总结果。
传递给collect方法参数是一个java.util.stream.Collector类型的对象。Collector对象实际上定义了一个如何把流中的元素聚集到最终结果的方法。最开始,工厂方法Collectors.toList()被用来返回一个描述了如何把流转变成一个List的Collector对象。后来Collectors类又内建了很多相似的collectors变量。例如,你可以用Collectors.groupingBy方法按消费者把发票分组,如下:
Putting It All Together
当你观察一下老式的代码你会发现每一个本地变量只被存储了一次,被下一段代码用了一次。当用Stream API之后,就完全消除了这个本地变量。
Parallel Streams
Stream API 支持方便的数据并行。换句话说,你可以明确地让流管道以并行的方式运行而不用关心底层的具体实现。在这背后,Stream API使用了Fork/Join框架充分利用了你机器的多核架构。
你所需要做的无非是用parallelStream()方法替换stream()方法。例如,下面代码显示如何并行地过滤金额高的发票:
然而,并不是所有的地方都可以用parallel Stream,从性能角度考虑,有几点你需要注意:
Splittability
parallel streams的内部实现依赖于将数据结构划分成可以让不同线程使用的难易程度。像数组这种数据结构很容易划分,而像链表或者文件这种数据结构很难划分。
Cost per element
越是计算流中单个元素花费的资源最高,应用并行越有意义。
Boxing
如果可能的话尽量用原始数据类型,这样可以占用更少的内存,也更缓存命中率也更高。
Size
流中元素的数据量越大越好,因为并行的成本会分摊到所有元素,并行节省的时间相对会更多。当然,这也跟单个元素计算的成本相关。
Number of cores
一般来说,核越多越好。
在实践中,如果你想提高代码的性能,你应该检测你代码的指标。Java Microbenchmark Harness (JMH) 是一个Oracle维护的流行的框架,你可以用它来帮你完成代码分析检测。如果不检测的话,简单的应用并行,代码的性能或许更差。
Summary
下面是本章的重点内容:
- 流是一列支持聚合操作的来自于不同数据源的元素列表
- 流有两种类型的操作方法:中间方法和终结方法
- 中间方法可以被连接起来形成管道
- 中间方法包括filter,map,distinct和sorted
- 终结方法处理流管道并返回一个结果
- 终结方法包括allMatch,collect和forEach
- Collectors是一个第应以了如何将流中的元素聚集到最终结果的方法,包括像List和Map一样的容器
- 流管道可以被并行地计算
- 当应用parallel stream 来提高性能时有很多个方面需要考虑,包括数据结构划分的难易程度,计算每个元素花费的高低,装箱的难易,数据量的多少和可用核的数量。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/9421.html