寻找流的特征

流的特征

Stream API依赖于一个特殊的对象,Spliterator接口的一个实例。这个接口的名字来源于这样一个事实:在Stream API中,spliterator 的角色与在Collection API中迭代器的角色相似。此外,因为Stream API支持并行处理,所以spliterator对象还控制流如何在处理并行化的不同cpu之间分割元素。名称是split和iterator的缩写。
详细介绍这个spliterator对象超出了本教程的范围。你需要知道的是这个spliterator对象带有流的特征。这些特征不是您经常使用的,但了解它们将帮助您在某些情况下编写更好、更高效的管道。
流的特征如下。
特征 评论
顺序 处理流元素的顺序很重要。
Double 该流处理的元素中没有double。
非空 该流中没有空元素。
排序 该流中的元素已被排序。
大小 这个流过程中已知的元素数量。
小尺寸 将这条流分裂产生两条大小的流。
并发和不可变不被包含在本教程中。
每个流在创建时都有设置或取消所有这些特征。
流可以通过两种方式创建。

  • 您可以从数据源创建流,我们介绍了几种不同的模式。
  • 每次在现有流上调用中间操作时,就会创建一个新流。

给定流的特征取决于创建它的源,或者创建它的流的特征,以及创建它的操作。如果你的流是用一个源创建的,那么它的特征依赖于那个源,如果你用另一个流创建它,那么它们将依赖于另一个流和你使用的操作类型。
让我们更详细地介绍每个特点。

有序流

有序流是用有序数据源创建的。想到的第一个例子可能是List接口的任何实例。还有其他的:Files.lines(path)和Pattern.splitAsStream(string)也会产生ORDERED流。
追踪流中元素的顺序可能会导致并行流的额外开销。如果您不需要这个特性,那么您可以通过在现有流上调用unordered()中间方法来删除它。这将返回一个没有此特性的新流。你为什么要这么做?在某些情况下,保持流顺序的代价可能很高,例如在使用并行流时。

排序流

已排序的流是已经被排序的流。可以从已排序的源(如TreeSet实例)或调用sorted()方法创建此流。流的实现可以使用已经排序的流来避免对已经排序的流再次排序。这种优化可能不会一直有用,因为排序后的流可能会再次使用与第一次使用的不同的比较器进行排序。
有一些中间操作可以清除排序特征。在下面的代码中,您可以看到字符串和filteredStream都是已排序的流,而长度则不是。

Collection<String> stringCollection = List.of("one", "two", "two", "three", "four", "five");

Stream<String> strings = stringCollection.stream().sorted();
Stream<String> filteredStrings = strings.filtered(s -> s.length() < 5);
Stream<Integer> lengths = filteredStrings.map(String::length);

对已排序的流进行映射或flatmapping ,会从生成的流中去除这个特性。

唯一元素流

DISTINCT流是指正在处理的元素之间没有重复的流。例如,在从HashSe实例t或调用distinct()中间方法构建流时,可以获得这种特征。
在对流进行过滤时保留DISTINCT特征,但在对流进行映射或flatmapping 时丢失。
让我们看看下面的例子。

Collection<String> stringCollection = List.of("one", "two", "two", "three", "four", "five");

Stream<String> strings = stringCollection.stream().distinct();
Stream<String> filteredStrings = strings.filtered(s -> s.length() < 5);
Stream<Integer> lengths = filteredStrings.map(String::length);

  • stringCollection.stream()不是DISTINCT,因为它是从List的实例构建的。
  • strings是DISTINCT,因为此流是通过调用DISTINCT()中间方法创建的。
  • filteredStrings仍然是DISTINCT:从流中删除元素不能创建重复元素。
  • 长度被映射,因此DISTINCT特征丢失。

非空流

非空流是指不包含空值的流。集合框架中有不接受空值的结构,包括ArrayDeque和并发结构,如ArrayBlockingQueue、ConcurrentSkipList,以及调用ConcurrentHashMap.newKeySet()返回的并发集。用Files.lines(path)和Pattern.splitAsStream(line)创建的流也是NONNULL流。
对于前面的特性,一些中间操作可以产生具有不同特性的流。

  • 过滤或排序一个非空流将返回一个非空流。
  • 在非空流上调用distinct()也会返回一个非空流。
  • 对非空流进行映射或flatmapping 将返回没有此特征的流

有大小和子大小的流

已知大小的流

当您想要使用并行流时,最后一个特性非常重要。在本教程的后面将详细介绍并行流。
一个已知大小的流知道它将处理多少个元素。从Collection的任何实例创建的流就是这样一个流,因为Collection接口有一个size()方法,所以获取这个数字很容易。
另一方面,在某些情况下,您知道您的流将处理有限数量的元素,但您不能知道这个数字,直到流处理的时候。
使用Files.lines(path)模式创建的流就是这种情况。您可以以字节为单位获取文本文件的大小,但此信息并没有告诉您该文本文件有多少行。您需要分析该文件以获得该信息。
这也是pattern. splitasstream (line)模式的情况。知道正在分析的字符串中有多少个字符并不能说明这个模式将产生多少个元素。

分割后流的大小

子流的大小与作为平行流计算时流被分割的方式有关。简单地说,并行化机制将一个流分成两部分,并将计算分配到CPU执行的不同可用核上。这种分割是由流使用的Spliterator的实例实现的。这个实现取决于您所使用的数据源。
假设您需要在ArrayList上打开一个流。这个列表的所有数据都保存在ArrayList实例的内部数组中。也许你还记得ArrayList对象的内部数组是一个紧凑数组,因为当你从这个数组中移除一个元素时,下面所有的元素都会向左移动一个单元格,这样就不会留下任何空位。
这使得拆分ArrayList变得简单。要拆分ArrayList的实例,只需将这个内部数组分成两部分,这两部分的元素数量相同。这使得在ArrayList 实例上创建的流SUBSIZED:你可以提前知道拆分后每个部分将包含多少元素。
假设现在需要打开HashSet实例上的流。HashSet将它的元素存储在数组中,但是这个数组的用法与ArrayList使用的数组不同。事实上,该数组的给定单元格中可以存储多个元素。拆分这个数组没有问题,但如果不计算它们,就无法提前知道每个部分中包含多少元素。即使你把这个数组从中间分割,你也永远不能确定在这两部分中元素的数量是相同的。这就是为什么在HashSet实例上创建的流是已知大小的而不已知子流大小的原因。
改造一条流可能会改变返回流的大小和子流大小的特性。

  • 对流进行映射和排序保留了大小和子流大小的特征。
  • flatmapping 、过滤和调用distinct()将删除这些特征。

对于并行计算来说,使用已知大小和子流大小总是更好的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值