主页 > 科技 > 正文

Java 8 中的 Streams API 详解

2019-04-15 21:13暂无阅读:1303评论:0

为什么需要 Stream

Stream 作为 Java 8 的一大亮点,它与 java.io 包里的 InputStream 和 OutputStream 是完全分歧的概念。

Java 8 中的 Stream 是对鸠合(Collection)对象功能的增加,它专注于对鸠合对象进行各类非常便当、高效的聚合把持(aggregate operation),或许多量量数据把持 (bulk data operation)。Stream API 借助于同样新显现的 Lambda 表达式,极大的提高编程效率和法式可读性。同时它供应串行和并行两种模式进行汇聚把持,并发模式可以充裕行使多核处理器的优势,使用 fork/join 并行体式来拆分义务和加快处理过程。平日编写并行代码很难并且轻易失足, 但使用 Stream API 无需编写一行多线程的代码,就能够很轻易地写出高机能的并发法式。所以说,Java 8 中首次显现的 java.util.stream 是一个函数式说话+多核时代综合影响的产品。什么是流

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

Stream 就如统一个迭代器(Iterator),单向,弗成来去,数据只能遍历一次,遍历过一次后即用尽了,就比如流水从眼前流过,一去不复返。

而和迭代器又分歧的是,Stream 能够并行化把持,迭代器只能号令式地、串行化把持。顾名思义,当使用串行体式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,个中每一个都在分歧的线程中处理,然后将究竟一路输出。Stream 的并行把持依靠于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分义务和加快处理过程。

Stream 的此外一大特点是,数据源自己能够是无限的。流的组成

当我们使用一个流的时候,平日包罗三个根基步伐:

获取一个数据源(source)→ 数据转换→执行把持获取想要的究竟,每次转换原有 Stream 对象不改变,返回一个新的 Stream 对象(能够有多次转换),这就许可对其把持能够像链条一般分列,酿成一个管道,如下图所示。

流管道 (Stream Pipeline) 的组成

有多种体式生成 Stream Source:

从 Collection 和数组

Collection.stream()

Collection.parallelStream()

Arrays.stream(T array) or Stream.of()

从 BufferedReaderjava.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。

在对于一个 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

list = Arrays.asList(strArray);

stream = list.stream();

需要注重的是,对于根基数值型,今朝有三种对应的包装类型 Stream:

IntStream、LongStream、DoubleStream。当然我们也能够用 Stream

、Stream

>、Stream

,然则 boxing 和 unboxing 会很耗时,所以稀奇为这三种根基数值型供应了对应的 Stream。

Java 8 中还没有供应另外数值型 Stream,因为这将导致扩增的内容较多。而常规的数值型聚合运算能够经由上面三种 Stream 进行。

数值流的组织:

IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);

IntStream.range(1, 3).forEach(System.out::println);

IntStream.rangeClosed(1, 3).forEach(System.out::println);

流转换为另外数据构造:

// 1. Array

String[] strArray1 = stream.toArray(String[]::new);

// 2. Collection

List

list1 = stream.collect(Collectors.toList());

List

list2 = stream.collect(Collectors.toCollection(ArrayList::new));

Set set1 = stream.collect(Collectors.toSet());

Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));

// 3. String

String str = stream.collect(Collectors.joining()).toString();

一个 Stream 只能够使用一次,上面的代码为了简练而反复使用了数次。

流的把持

接下来,当把一个数据构造包装成 Stream 后,就要起头对里面的元素进行各类把持了。常见的把持能够归类如下。

Intermediate:

map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered

Terminal:

forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator

Short-circuiting:

anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit