引入流
- 流是什么
集合是Java中使用最多的API,但集合操作却远远算不上完美:
1.很多业务逻辑都涉及类似于数据库的操作,比如对几道菜按照类别进行分组 (比如全素 菜肴),或查找出最贵的菜。你自己用迭代器重新实现过这些操作多少遍?
2.要是要处理大量元素又该怎么办呢?为了提高性能,你需要并行处理,并利用多核架构
流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不 是临时编写一个实现)
1.声明性——更简洁,更易读
2.可复合——更灵活
3.可并行——性能更好
那么,流到底是什么呢?简短的定义就是“从支持数据处理操作的源生成的元素序列”
1.元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序 值。
2.源:流会使用一个提供数据的源,如集合、数组或输入/输出资源。
3.数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中 的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执 行,也可并行执行。
4.流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。
5.内部迭代:与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。
return dishes.stream()
.filter(d -> d.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.limit(3)
.collect(toList());
在本例中,我们先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜 单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit 和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流 水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果.
2-流-示例.png
- 流与集合
粗略地说,集合与流之间的差异就在于什么时候进行计算。集合是一个内存中的数据结构, 它包含数据结构中目前所有的值——集合中的每个元素都得先算出来才能添加到集合中。(你可 以往集合里加东西或者删东西,但是不管什么时候,集合中的每个元素都是放在内存里的,元素 都得先算出来才能成为集合的一部分。)
相比之下,流则是在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计 算的。这是一种生产者-消费者的关系。从另一个角度来说,流就 像是一个延迟创建的集合:只有在消费者要求的时候才会计算值.
和迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了.
外部迭代和内部迭代
使用Collection接口需要用户去做迭代(比如用for-each),这称为外部迭代。 相反, Streams库使用内部迭代——它帮你把迭代做了,还把得到的流值存在了某个地方,你只要给出 一个函数说要干什么就可以了.
-
流操作
2-流-操作.png
1.中间操作
诸如filter或sorted等中间操作会返回另一个流。这让多个操作可以连接起来形成一个查 询。重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理
2.终端操作
终端操作会从流的流水线生成结果
3.流使用
总而言之,流的使用一般包括三件事:
- 一个数据源(如集合)来执行一个查询;
- 一个中间操作链,形成一条流的流水线;
- 一个终端操作,执行流水线,并能生成结果
使用流
让Stream API管理如何处理数据。这样Stream API就可 以在背后进行多种优化。使用内部迭代的话,Stream API可以决定并行运行你的代码。这 要是用外部迭代的话就办不到了,因为你只能用单一线程挨个迭代。
-
筛选切片
2-流-筛选.png
筛选各异的元素
截断流
跳过 -
映射
map
flatMap
2-流-flatmap.png
-
查找匹配
-
归约
如何把一个流中的元素组合起来,使用reduce操作来表达更复杂的查
询,比如“计算菜单中的总卡路里”或“菜单中卡路里最高的菜是哪一个”。此类查询需要将流中所有元素反复结合起来,得到一个值,比如一个Integer。
2-流-reduce.png -
交易员Demo
(1) 找出2011年发生的所有交易,并按交易额排序(从低到高)。
(2) 交易员都在哪些不同的城市工作过?
(3) 查找所有来自于剑桥的交易员,并按姓名排序。
(4) 返回所有交易员的姓名字符串,按字母顺序排序。
(5) 有没有交易员是在米兰工作的?
(6) 打印生活在剑桥的交易员的所有交易额。 (7) 所有交易中,最高的交易额是多少?
(8) 找到交易额最小的交易。 -
数值流
-
构建流
1.值创建类
2.数组生成流
3.文件生成流
4.由函数生成流,创建无限流
用流收集数据
- 归约和汇总
collect是一个归约操作,就像reduce一样可以接 受各种做法作为参数,将流中的元素累积成一个汇总结果。具体的做法是通过定义新的 Collector接口来定义的,因此区分Collection、Collector和collect是很重要的
下面是一些查询的例子,看看你用collect和收集器能够做什么。
1.对一个交易列表按货币分组,获得该货币的所有交易额总和(返回一个Map<Currency,Integer>)。
2.将交易列表分成两组:贵的和不贵的(返回一个Map<Boolean, List<Transaction>>)。
3.创建多级分组,比如按城市对交易分组,然后进一步按照贵或不贵分组(返回一个 Map<Boolean, List<Transaction>>)
前一个例子清楚地展示了函数式编程相对于指令式编程的一个主要优势:你只需指出希望的 结果——“做什么”,而不用操心执行的步骤——“如何做”。在上一个例子里,传递给collect 方法的参数是Collector接口的一个实现,也就是给Stream中元素做汇总的方法,toList只是说“按顺序给每个元素生成一个列表”;在本例中,groupingBy说的是“生成一个 Map,它的键是(货币)桶,值则是桶中那些元素的列表”。
2-流-收集.png
-
预定义收集器
预定义收集器的功能,也就是那些可以从Collectors 类提供的工厂方法(例如groupingBy)创建的收集器。它们主要提供了三大功能:
1.将流元素归约和汇总为一个值
2.元素分组
3.元素分区 -
归约 汇总
-
广义的归约汇总
reducing方法创建的收集器
起始值
转换函数
累计函数 -
分组
2-流-分组.png
-
分区
分区是分组的特殊情况:由一个谓词(返回一个布尔值的函数)作为分类函数,它称分区函 数。分区函数返回一个布尔值,这意味着得到的分组Map的键类型是Boolean,于是它最多可以 分为两组——true是一组,false是一组 -
收集器接口
自定义归约操作
顺序归约过程.png
并行数据处理与性能
- 并行流
在Java 7之前,并行处理数据集合非常麻烦。第一,你得明确地把包含数据的数据结构分成若干子部分。第二,你要给每个子部分分配一个独立的线程。第三,你需要在恰当的时候对它们进行同步来避免不希望出现的竞争条件,等待所有线程完成,最后把这些部分结果合并起来。 Java 7引入了一个叫作分支/合并的框架,让这些操作更稳定、更不易出错
高效使用并行流
把顺序流转成并行流轻而易举,但却不一定是好事。我们在本节中
已经指出,并行流并不总是比顺序流快。此外,并行流有时候会和你的直觉不一致,所
以在考虑选择顺序流还是并行流时,第一个也是最重要的建议就是用适当的基准来检查
其性能
1.留意装箱。自动装箱和拆箱操作会大大降低性能。 Java 8中有原始类型流(IntStream、
LongStream、 DoubleStream)来避免这种操作,但凡有可能都应该用这些流
2.有些操作本身在并行流上的性能就比顺序流差。特别是limit和findFirst等依赖于元
素顺序的操作,它们在并行流上执行的代价非常大
3.还要考虑流的操作流水线的总计算成本
4.对于较小的数据量,选择并行流几乎从来都不是一个好的决定
5.考虑流背后的数据结构是否易于分解
6.考虑终端操作中合并步骤的代价是大是小
-
fork/join
分支合并过程.png -
Spliterator
网友评论