首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

java.util.stream 库简介(2)

java.util.stream 库简介(2)

流管道剖析所有流计算都有一种共同的结构:它们具有一个流来源、0                或多个中间操作,以及一个终止操作。流的元素可以是对象引用                (Stream<String>),也可以是原始整数 (IntStream)、长整型                (LongStream) 或双精度 (DoubleStream)。
因为 Java 程序使用的大部分数据都已存储在集合中,所以许多流计算使用集合作为它们的来源。JDK 中的                Collection                实现都已增强,可充当高效的流来源。但是,还存在其他可能的流来源,比如数组、生成器函数或内置的工厂(比如数字范围),而且(如本系列中的  所示)可以编写自定义的流适配器,以便可以将任意数据源充当流来源。表 1 给出了 JDK 中的一些流生成方法。
表 1. JDK 中的流来源方法描述Collection.stream()使用一个集合的元素创建一个流。Stream.of(T...)使用传递给工厂方法的参数创建一个流。Stream.of(T[])使用一个数组的元素创建一个流。Stream.empty()创建一个空流。Stream.iterate(T first, BinaryOperator<T>                            f)创建一个包含序列                            first, f(first), f(f(first)),                                ...                            的无限流Stream.iterate(T first, Predicate<T> test,                                BinaryOperator<T> f)(仅限                            Java 9)类似于                            Stream.iterate(T first, BinaryOperator<T>                            f),但流在测试预期返回                            false 的第一个元素上终止。Stream.generate(Supplier<T>                            f)使用一个生成器函数创建一个无限流。IntStream.range(lower,                            upper)创建一个由下限到上限(不含)之间的元素组成的                            IntStream。IntStream.rangeClosed(lower,                            upper)创建一个由下限到上限(含)之间的元素组成的                            IntStream。BufferedReader.lines()创建一个有来自                            BufferedReader 的行组成的流。BitSet.stream()创建一个由 BitSet                            中的设置位的索引组成的 IntStream。Stream.chars()创建一个与 String                            中的字符对应的 IntStream。
中间操作负责将一个流转换为另一个流,中间操作包括                filter()(选择与条件匹配的元素)、map()(根据函数来转换元素)、distinct()(删除重复)、limit()(在特定大小处截断流)和                sorted()。一些操作(比如                    mapToInt())获取一种类型的流并返回一种不同类型的流; 中的示例的开头处有一个 Stream<Transaction>,它随后被转换为                IntStream。表 2 给出了一些中间流操作。
表 2. 中间流操作操作内容filter(Predicate<T>)                            与预期匹配的流的元素map(Function<T, U>)                            将提供的函数应用于流的元素的结果flatMap(Function<T,                            Stream<U>>将提供的流处理函数应用于流元素后获得的流元素distinct() 已删除了重复的流元素sorted() 按自然顺序排序的流元素Sorted(Comparator<T>)                            按提供的比较符排序的流元素limit(long) 截断至所提供长度的流元素skip(long) 丢弃了前 N 个元素的流元素takeWhile(Predicate<T>)(仅限 Java                            9)在第一个提供的预期不是 true 的元素处阶段的流元素dropWhile(Predicate<T>)(仅限 Java                            9)丢弃了所提供的预期为 true 的初始元素分段的流元素
中间操作始终是惰性的:调用中间操作只会设置流管道的下一个阶段,不会启动任何操作。重建操作可进一步划分为无状态                    和有状态 操作。无状态操作(比如 filter() 或                map())可独立处理每个元素,而有状态操作(比如 sorted() 或                distinct())可合并以前看到的影响其他元素处理的元素状态。
数据集的处理在执行终止操作时开始,比如缩减(sum() 或 max())、应用                (forEach()) 或搜索 (findFirst())                操作。终止操作会生成一个结果或副作用。执行终止操作时,会终止流管道,如果您想再次遍历同一个数据集,可以设置一个新的流管道。表 3                给出了一些终止流操作。
表 3. 终止流操作操作描述forEach(Consumer<T> action)                            将提供的操作应用于流的每个元素。toArray() 使用流的元素创建一个数组。reduce(...) 将流的元素聚合为一个汇总值。collect(...)                        将流的元素聚合到一个汇总结果容器中。min(Comparator<T>)                            通过比较符返回流的最小元素。max(Comparator<T>)                            通过比较符返回流的最大元素。count() 返回流的大小。{any,all,none}Match(Predicate<T>)返回流的任何/所有元素是否与提供的预期相匹配。findFirst() 返回流的第一个元素(如果有)。findAny() 返回流的任何元素(如果有)。
流与集合比较尽管流在表面上可能类似于集合(您可以认为二者都包含数据),但事实上,它们完全不同。集合是一种数据结构;它的主要关注点是在内存中组织数据,而且集合会在一段时间内持久存在。集合通常可用作流管道的来源或目标,但流的关注点是计算,而不是数据。数据来自其他任何地方(集合、数组、生成器函数或                I/O                通道),而且可通过一个计算步骤管道处理来生成结果或副作用,在此刻,流已经完成了。流没有为它们处理的元素提供存储空间,而且流的生命周期更像一个时间点                —                    调用终止操作。不同于集合,流也可以是无限的;相应地,一些操作(limit()、findFirst())是短路,而且可在无限流上运行有限的计算。
集合和流在执行操作的方式上也不同。集合上的操作是急切和突变性的;在 List 上调用                remove()                方法时,调用返回后,您知道列表状态会发生改变,以反映指定元素的删除。对于流,只有终止操作是急切的;其他操作都是惰性的。流操作表示其输入(也是流)上的功能转换,而不是数据集上的突变性操作(过滤一个流会生成一个新流,新流的元素是输入流的子集,但没有从来源删除任何元素)。
将流管道表达为功能转换序列可以实现多种有用的执行战略,比如惰性短路                或操作融合。短路使得管道能够成功终止,而不必检查所有数据;类似 “找到第一笔超过 1000 美元的交易”                这样的查询不需要在找到匹配值后检查其他任何交易。操作融合表示,可在数据上的一轮中执行多个操作;在  的示例中,3 个操作组合成了数据上的一轮操作,而不是首先选择所有匹配的交易,然后选择所有对应的金额,最后对它们求和。
类似  和                 中的查询的命令版本通常依靠物化集合来获得中间计算的结果,比如过滤或映射的结果。这些结果不仅可能让代码变得杂乱,还可能让执行变得混乱。中间集合的物化仅作用于实现,而不作用于结果,而且它使用计算周期将中间结果组织为将会被丢弃的数据结构。
相反,流管道将它们的操作融合到数据上尽可能少的轮次中,通常为单轮。(有状态中间操作,比如排序,可引入对多轮执行必不可少的障碍点。)流管道的每个阶段惰性地生成它的元素,仅在需要时计算元素,并直接将它们提供给下一阶段。您不需要使用集合来保存过滤或映射的中间结果,所以省去了填充(和垃圾收集)中间集合的工作。另外,遵循                “深度优先” 而不是 “宽度优先” 的执行战略(跟踪一个数据元素在整个管道中的深度),会让被处理的操作在缓存中变得更                “热”,所以您可以将更多时间用于计算,花更少时间来等待数据。
除了将流用于计算之外,您可能还希望考虑通过 API                方法使用流来返回聚合结果,而在以前,您可能返回一个数组或集合。返回流的效率通常更高一些,因为您不需要将所有数据复制到一个新数组或集合中。返回流通常更加灵活;库选择返回的集合形式可能不是调用方所需要的,而且很容易将流转换为任何集合类型。(返回流不合适,而返回物化集合更合适的主要情形是,调用方需要查看某个时间点的状态的一致快照。)
返回列表