JDK8----集合之流式(Streams)操做

JDK8 —- 集合之流式(Streams)操做javascript

为何须要 Stream

  Stream 做为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是彻底不一样的概念。它也不一样于 StAX 对 XML 解析的 Stream,也不是 Amazon Kinesis 对大数据实时处理的 Stream。Java 8 中的 Stream 是对集合(Collection)对象功能的加强,它专一于对集合对象进行各类很是便利、高效的聚合操做(aggregate operation),或者大批量数据操做 (bulk data operation)。Stream API 借助于一样新出现的 Lambda表达式,极大的提升编程效率和程序可读性。同时它提供串行和并行两种模式进行汇聚操做,并发模式可以充分利用多核处理器的优点,使用 fork/join 并行方式来拆分任务和加速处理过程。一般编写并行代码很难并且容易出错, 但使用 Stream API 无需编写一行多线程的代码,就能够很方便地写出高性能的并发程序。因此说,Java 8 中首次出现的 java.util.stream 是一个函数式语言+多核时代综合影响的产物java

什么是聚合操做
  在传统的 J2EE 应用中,Java 代码常常不得不依赖于关系型数据库的聚合操做来完成诸如:
  * 客户每个月平均消费金额
  * 最昂贵的在售商品
  * 本周完成的有效订单(排除了无效的)
  * 取十个数据样本做为首页推荐
这类的操做。
  但在当今这个数据大爆炸的时代,在数据来源多样化、数据海量化的今天,不少时候不得不脱离 RDBMS,或者以底层返回的数据为基础进行更上层的数据统计。而 Java 的集合 API 中,仅仅有极少许的辅助型方法,更多的时候是程序员须要用 Iterator 来遍历集合,完成相关的聚合应用逻辑。这是一种远不够高效、笨拙的方法。在 Java 7 中,若是要发现 type 为 grocery 的全部交易,而后返回以交易值降序排序好的交易 ID 集合,咱们须要这样写:程序员

List<Transaction> groceryTransactions = new ArrayList<>();
        for (Transaction t : transactions) {
            if (t.getType() == Transaction.GROCERY) {
                groceryTransactions.add(t);
            }
        }
        Collections.sort(groceryTransactions, new Comparator() {
            public int compare(Transaction t1, Transaction t2) {
                return t2.getValue().compareTo(t1.getValue());
            }
        });

        List<Integer> transactionIds = new ArrayList<>();
        for (Transaction t : groceryTransactions) {
            transactionIds.add(t.getId());
        }

  而在 Java 8 使用 Stream,代码更加简洁易读;并且使用并发模式,程序执行速度更快。web

List<Integer> transactionsIds = transactions.parallelStream().
                filter(t -> t.getType() == Transaction.GROCERY).
                sorted(comparing(Transaction::getValue).reversed()).
                map(Transaction::getId).
                collect(toList());

集合之流式操做
  Java 8 引入了流式操做(Stream),经过该操做能够实现对集合(Collection)的并行处理和函数式操做。根据操做返回的结果不一样,流式操做分为中间操做和最终操做两种。最终操做返回一特定类型的结果,而中间操做返回流自己,这样就能够将多个操做依次串联起来。根据流的并发性,流又能够分为串行和并行两种。流式操做实现了集合的过滤、排序、映射等功能。
Stream 和 Collection 集合的区别:Collection 是一种静态的内存数据结构,而 Stream 是有关计算的。前者是主要面向内存,存储在内存中,后者主要是面向 CPU,经过 CPU 实现计算。算法

串行和并行的流
  流有串行和并行两种,串行流上的操做是在一个线程中依次完成,而并行流则是在多个线程上同时执行。并行与串行的流能够相互切换:经过 stream.sequential() 返回串行的流,经过 stream.parallel() 返回并行的流。相比较串行的流,并行的流能够很大程度上提升程序的执行效率。数据库

Stream 总览

什么是流
  Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操做;高级版本的 Stream,用户只要给出须要对其包含的元素执行什么操做,好比 “过滤掉长度大于 10 的字符串”、“获取每一个字符串的首字母”等,Stream 会隐式地在内部进行遍历,作出相应的数据转换。
  Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就比如流水从面前流过,一去不复返。
  而和迭代器又不一样的是,Stream 能够并行化操做,迭代器只能命令式地、串行化操做。顾名思义,当使用串行方式去遍历时,每一个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分红多个段,其中每个都在不一样的线程中处理,而后将结果一块儿输出。Stream 的并行操做依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。Java 的并行 API 演变历程基本以下:
  1.0-1.4 中的 java.lang.Thread
  5.0 中的 java.util.concurrent
  6.0 中的 Phasers 等
  7.0 中的 Fork/Join 框架
  8.0 中的 Lambda
Stream 的另一大特色是,数据源自己能够是无限的编程

流的构成
  当咱们使用一个流的时候,一般包括三个基本步骤:
  获取一个数据源(source)→ 数据转换→执行操做获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(能够有屡次转换),这就容许对其操做能够像链条同样排列,变成一个管道,以下图所示。
图 1. 流管道 (Stream Pipeline) 的构成
数据结构

流的操做类型分为两种:
  Intermediate:一个流能够后面跟随零个或多个 intermediate 操做。其目的主要是打开流,作出某种程度的数据映射/过滤,而后返回一个新的流,交给下一个操做使用。这类操做都是惰性化的(lazy),就是说,仅仅调用到这类方法,并无真正开始流的遍历。
  Terminal:一个流只能有一个 terminal 操做,当这个操做执行后,流就被使用“光”了,没法再被操做。因此这一定是流的最后一个操做。Terminal 操做的执行,才会真正开始流的遍历,而且会生成一个结果,或者一个 side effect。
  
  在对于一个 Stream 进行屡次转换操做 (Intermediate 操做),每次都对 Stream 的每一个元素进行转换,并且是执行屡次,这样时间复杂度就是 N(转换次数)个 for 循环里把全部操做都作掉的总和吗?其实不是这样的,转换操做都是 lazy 的,多个转换操做只会在 Terminal 操做的时候融合起来,一次循环完成。咱们能够这样简单的理解,Stream 里有个操做函数的集合,每次转换操做就是把转换函数放入这个集合中,在 Terminal 操做的时候循环 Stream 对应的集合,而后对每一个元素执行全部的函数。多线程

还有一种操做被称为 short-circuiting。用以指:
  对于一个 intermediate 操做,若是它接受的是一个无限大(infinite/unbounded)的 Stream,但返回一个有限的新 Stream。
  对于一个 terminal 操做,若是它接受的是一个无限大的 Stream,但能在有限的时间计算出结果。
当操做一个无限大的 Stream,而又但愿在有限时间内完成操做,则在管道内拥有一个 short-circuiting 操做是必要非充分条件。并发

流的使用详解
  简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者致使一个反作用(side effect)。
  
流的构造与转换
  下面提供最多见的几种构造 Stream 的样例。

// 1. Individual values
        Stream stream = Stream.of("a", "b", "c");

        // 2. Arrays
        String[] strArray = new String[]{"a", "b", "c"};
        stream = Stream.of(strArray);
        stream = Arrays.stream(strArray);

        // 3. Collections
        List<String> list = Arrays.asList(strArray);
        stream = list.stream();

须要注意的是,对于基本数值型,目前有三种对应的包装类型 Stream:   IntStream、LongStream、DoubleStream。固然咱们也能够用 Stream、Stream >、Stream,可是 boxing 和 unboxing 会很耗时,因此特别为这三种基本数值型提供了对应的 Stream。   Java 8 中尚未提供其它数值型 Stream,由于这将致使扩增的内容较多。而常规的数值型聚合运算能够经过上面三种 Stream 进行。