Java 8 中的 Streams API 简介

Java 的集合 API 中,仅仅有极少量的辅助型方法,更多的时候是程序员需要用 Iterator 来遍历集合,完成相关的聚合应用逻辑。这是一种远不够高效、笨拙的方法。 


Streams 简介 

为什么需要 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 是一个函数式语言+多核时代综合影响的产物。

 

什么是聚合操作

在传统的 J2EE 应用中,Java 代码经常不得不依赖于关系型数据库的聚合操作来完成诸如:

  • 客户每月平均消费金额
  • 最昂贵的在售商品
  • 本周完成的有效订单(排除了无效的)
  • 取十个数据样本作为首页推荐

这类的操作。


举个示例代码来说明,在 Java 7 中,如果要发现 type 为 grocery 的所有交易,然后返回以交易值降序排序好的交易 ID 集合,我们需要这样写:

排序、取值实现:

// Java 7
List<Transaction> groceryTransactions = new Arraylist<>();
// 第一步:For循环筛选出满足条件的对象添加至集合:groceryTransactions
for(Transaction t: transactions){
   if(t.getType() == Transaction.GROCERY){
     groceryTransactions.add(t);
   }
}
// 第二步:将集合:groceryTransactions 执行排序
Collections.sort(groceryTransactions, new Comparator(){
   public int compare(Transaction t1, Transaction t2){
     return t2.getValue().compareTo(t1.getValue());
   }
});
List<Integer> transactionIds = new ArrayList<>();
// 第三步:将排序好的列表通过For循环将所以的ID添加到新的集合:transactionIds
for(Transaction t: groceryTransactions){
   transactionsIds.add(t.getId());
}

而在 Java 8 使用 Stream,代码如下:

// Java 8
List<Integer> transactionsIds = transactions.parallelStream()
    .filter(t -> t.getType() == Transaction.GROCERY) // 筛选
    .sorted(comparing(Transaction::getValue).reversed()) // 排序
    .map(Transaction::getId) // 集合转换:返回一个列表,其中包含将给定转换函数应用于原始集合中的每个元素的结果
    .collect(toList()); // Stream to List

是不是感觉 代码更加简洁易读;而且Java 8 的 Stream 使用并发模式,程序执行速度更快。总之 很🐂 !!!

那什么是流呢?

Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。

Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。

而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。


当我们使用一个流的时候,通常包括三个基本步骤:

获取一个数据源(source) → 数据转换 → 执行操作获取想要的结果,
简单的说:每次转换,原有 Stream 对象不改变, 然后通过一系列 流 的操作,返回一个新的 Stream 对象(过程可以有多次转换)。

 

那怎么去生成 Stream Source?

有多种方式生成 Stream Source:

  • 从 Collection 和数组

    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream(T array) or Stream.of()

从 BufferedReader

  • java.io.BufferedReader.lines()
  • 静态工厂

  • java.util.stream.IntStream.range()

  • java.nio.file.Files.walk()

  • 自己构建

    • java.util.Spliterator

其它

  • Random.ints()
  • BitSet.stream()
  • Pattern.splitAsStream(java.lang.CharSequence)
  • JarFile.stream()

 

流的操作类型有哪些?

分为两种:

  • Intermediate :一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
  • Terminal :一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

对于以上的说明,再阐述一下,希望可以更容易的理解,这里我们假设:Intermediate类型的操作有 A(), B(), C(), D(), E() 。   Terminal类型的操作有 a(), b() 。 此时我们可以执行A,B,C,D,E操作中的一个或多个,但如果需要执行a,b操作,只能执行其中一个且必须放在最后执行。

示例可以简单写下:

Collection.stream().A().C().D().E().b() 

 

流的操作时间复杂度:

在对于一个 Stream 进行多次转换操作 (Intermediate 操作),每次都对 Stream 的每个元素进行转换,而且是执行多次,这样时间复杂度就是 N(转换次数)个 for 循环里把所有操作都完成的总和吗?

- 其实不是这样的,转换操作都是 lazy 的,多个转换操作只会在 Terminal 操作的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream 里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在 Terminal 操作的时候循环 Stream 对应的集合,然后对每个元素执行所有的函数。

还是 以上述的示例 来说明:

它经历了5次的流操作:A,C,D,E,b; 该示例的时间复杂度 并不是 5(转换次数)* For 循环的时间复杂度;由于多个转换操作只会在 Terminal 操作的时候融合起来,也就是只会在b操作的执行的时候一并完成,即完成该示例的时间复杂度就等于 一次For 循环的时间复杂度。

 

这里再给一个流操作的示例:

int sum = widgets.stream() // 生成流 stream
          .filter(w -> w.getColor() == RED) // 筛选
          .mapToInt(w -> w.getWeight()) // 集合转换
          .sum(); // 求和

 

>>>>>>>> Stream 流的使用

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值