Java 8-Stream API

流处理

流是一系列数据项,一次只生产一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能是另一个程序的输入流。

流,简短的定义就是“从支持数据处理操作的源生成的元素序列”

  • 源:流会使用一个提供数据的源,如集合、数组或输入输出资源。
  • 元素序列:就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值。集合讲的是数据,流讲的是计算。
  • 数据处理操作:流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的常用操作,如filter、map、reduce、find、match、sort等。流操作可以顺序执行,也可以并行执行。
  • 流水线:很多流操作本身会返回一个流,这样多个操作就可以链接起来,形成一个大的流水线。流水线的操作可以看作对数据源进行数据库式查询。
  • 内部迭代:与使用迭代器显示迭代的集合不同,流的迭代操作时在背后进行的。
cat file1 file2 | tr "[A-z]" "a-z" | sort | tail -3

Unix的cat命令会把两个文件连接起来创建一个流,tr会转换流中的字符,sort会对流中的行进行排序,而tail -3则给出流的最后三行。Unix命令允许这些程序通过管道(|)连接在一起。

在Unix中,命令(cat、tr、sort和tail)是同时执行的,这样sort就可以在cat或tr完成前先处理头几行。

就像汽车组装流水线一样,汽车排队进入加工站,每个加工站会接收、修改汽车,然后将之传递给下一站做进一步的处理。尽管流水线实际上是一个序列,但不同加工站的运行一般是并行的。

基于这一思想,Java 8在java.util.stream中添加了一个Stream API。Stream API的很多方法可以链接起来形成一个复杂的流水线,就像例子里面链接起来的Unix命令一样。

这时可以在一个更高的抽象层次上写Java 8程序了:思路变成了把这样的流变成那样的流(就像写数据库查询语句时的那种思路),而不是一次只处理一个项目。另一个好处是,Java 8可以透明地把输入的不相关部分拿到几个CPU内核上去分别执行你的Stream操作流水线—这几乎是免费的并行,用不着费劲搞Thread了。

Stream API优点

Stream API可以让你写出这样的代码

  • 声明性—更简洁,更易读
  • 可复合—更灵活
  • 可并行—性能更好

外部迭代和内部迭代

迭代器类似,流只能遍历一次。遍历完之后,我们就说这个流已经被消费掉了。你可以从原始数据源那里在获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了)。以下代码会抛出一个异常,说流已被消费掉了。

Stream<Dish> s=menu.stream();
s.forEach(System.out::println);
//java.lang.IllegalStateException:流已经被操作或关闭
s.forEach(System.out::println);

使用Collection接口需要用户去做迭代(比如用for-each),这称为外部迭代。Streams库使用了内部迭代,它帮你把迭代做了,还把得到的流存在了某个地方,你只要给出一个函数说要干什么就可以了。

外部迭代一个集合,显式地取出每个项目再加以处理。

内部迭代时,项目可以透明地并行处理,或者用更优化的顺序进行处理。Streams库的内部迭代可以自动选择一种适合你硬件的数据表示和并行实现。

流操作

流的使用一般包括三件事:

  • 一个数据源(如集合)来执行一个查询
  • 一个中间操作链,形成一条流的流水线
  • 一个终端操作,执行流水线,并能生成结果

流的流水线背后的理念类似于构建器模式。在构建器模式中有一个调用链用来设置一套配置(对流来说这就是一个中间操作链),接着是调用built方法(对流来说是终端操作)。

猜你喜欢

转载自blog.csdn.net/zsx157326/article/details/80886265