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

对集合进行代码简洁易读,清晰,不一定包含高效的转换、过滤、聚合等操作,“高级版”的迭代器,并提供方便的并发操作(但并发功能慎用)。

WIKI  stream的意义是更优雅(简洁)的实现一些数据操作,但效率不一定比传统写法高.因为其简洁性,不应该在其中写入太多代码.

什么是流

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

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

而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。Java 的并行 API 演变历程基本如下:

  1. 1.0-1.4 中的 java.lang.Thread
  2. 5.0 中的 java.util.concurrent
  3. 6.0 中的 Phasers 等
  4. 7.0 中的 Fork/Join 框架
  5. 8.0 中的 Lambda

Stream 的另外一大特点是,数据源本身可以是无限的。

生成流

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

获取一个数据源(source)→ 数据转换→执行操作获取想要的结果,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道.

1.Stream

从Collection或者Array

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

从BufferedReader

java.io.BufferedReader.lines()

生成

Stream.generate(Supplier<T> s)

Stream.iterate(final T seed, final UnaryOperator<T> f)

自己构建

java.util.Spliterator

2.静态工厂 IntStream/LongStream/DoubleStream

java.util.stream.IntStream.range();
java.nio.file.Files.walk();

3.Stream与基本类型XXXStream的转换

Stream.mapToLong()

IntStream.boxed();

流的操作类型分为两种:

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

WIKI 流是一个懒加载的模式 未结束流前不会进行操作,只要结束流以后才会操作返回结果.

                                                                                            Stream操作分类

中间操作(Intermediate operations)

无状态(Stateless)

map() mapToInt() mapToLong() mapToDouble()

flatMap() flatMapToInt() flatMapToLong() flatMapToDouble()

filter()

peek()

unordered()

有状态(Stateful)

distinct() sorted() limit() skip()

结束操作(Terminal operations)

非短路操作

forEach() forEachOrdered() toArray() reduce() collect() max() min() count()

短路操作(short-circuiting)

anyMatch() allMatch() noneMatch() findFirst() findAny()

实例:

int sum = widgets.stream()
.filter(w -> w.getColor() == RED)
 .mapToInt(w -> w.getWeight())
 .sum();

stream() 获取当前小物件的 source,filter 和 mapToInt 为 intermediate 操作,进行数据筛选和转换,最后一个 sum() 为 terminal 操作,对符合条件的全部小物件作重量求和。

WIKI  流的简单使用就是对一串输入数据的处理,本来非常复杂的处理可以使用简洁的代码实现

复杂使用

简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect)。

come soon...

并发流

1.生成并发流

Collection.parallelStream();

Stream.parallel()

2.fork/join模式自动对数据拆分,充分利用多核

3.全部并行流默认均使用同一个线程池

java.util.concurrent.ForkJoinPool.common

怎么用其他线程池?

4.不保证元素顺序

5.什么时候用并发流?

较“重”的定时任务

指定线程池

猜你喜欢

转载自blog.csdn.net/qq_30054997/article/details/81134626