【问题标题】:Which operations preserve order [duplicate]哪些操作保留顺序[重复]
【发布时间】:2018-02-09 05:48:08
【问题描述】:

TL;DR; 我正在寻找一个可以查找某个中间或终端操作的地方。我在哪里可以找到这样的文档?

编辑这不是 How to ensure order of processing in java8 streams? 的重复,因为该问题没有提供完整的操作列表。

背景

the package documentation 说:

流是否有遇到顺序取决于源和中间操作

this excellent stackoverflow answer中重复了

为了确保在整个流操作中保持顺序,您必须研究流的源,所有中间操作和终端操作的文档,以了解它们是否保持顺序(或源是否有顺序)首先)。

这一切都很好,但是我应该查看哪些文档? the package documentation 在一个示例中提到 map 保证排序,但它没有详尽的列表。 javadoc for the Stream class 记录了一些中间操作,但不是全部。 以map为例:

返回一个流,其中包含将给定函数应用于该流的元素的结果。

这是一个中间操作。

filter

返回一个由该流中与给定谓词匹配的元素组成的流。

这是一个中间操作。

这些都没有描述它们是否保持排序。

This stackoverflow answer 声称:

实际上每个中间操作都默认保留一个顺序。唯一的例外是:

  • unordered() 删除排序约束。
  • sorted() 改变顺序。

如果没有明确指定,您可以假设操作保持顺序。甚至 distinct() 保持顺序,尽管它增加了并行流的复杂性。

但是有任何官方文档可以支持吗?

额外积分????

实际上有两个单独的排序问题。

  1. 操作的输出是否与输入保持相同的顺序?
  2. 操作是否在每个元素上按顺序执行。

例如,并行 map 操作可以按任意顺序遍历所有元素(违反 2.),但仍保持返回流中的顺序(遵守 1.)

【问题讨论】:

标签: java java-8 java-stream


【解决方案1】:

经过对源代码的一些研究,我总结了以下表格:

取自:Java streams - Part 6 - Spliterator

下表显示了允许修改字符的操作类型:

|                        | DISTICTS | SORTED | ORDERED | SIZED | SHORT_CIRCUIT |
| ---------------------- | -------- | ------ | ------- | ----- | --------------|
| source stream          | Y        | Y      | Y       | Y     | N             |
| intermediate operation | PCI      | PCI    | PCI     | PC    | PI            |
| terminal operation     | N        | N      | PC      | N     | PI            |
  • Y - 允许拥有
  • P - 五月蜜饯
  • C - May 清除。
  • I - May 注射。
  • N - 无效;与操作无关。

取自Java streams - Stream methods characteristics table

下表显示了每个中间操作/终端操作可以打开和关闭的特征和标志:(SHORT_CIRCUIT 仅在 @987654326 的上下文中相关@标志)

注意:除了带有CI(清除和注入)标志的单元格之外,每个单元格都会添加P(保留)标志。

|                  |  DISTINCT |  SORTED |  ORDERED |  SIZED |  SHORT_CIRCUIT |
| ---------------- | ----------| --------| ---------| -------| ---------------|
|  filter          |           |         |          |  C     |                |
|  forEach         |           |         |  C       |        |                |
|  forEachOrdered  |           |         |          |        |                |
|  allMatch        |           |         |  C       |        |  I             |
|  distinct        |  I        |         |          |  C     |                |
|  flatMap         |  C        |  C      |          |  C     |                |
|  anyMatch        |           |         |  C       |        |  I             |
|  collect         |           |         |          |        |                |
|  unOrdered       |           |         |  C       |        |                |
|  count           |  C        |  C      |  C       |  C     |                |
|  findAny         |           |         |  C       |        |  I             |
|  findFirst       |           |         |          |        |  I             |
|  flatMapToXXX    |  C        |  C      |          |  C     |                |
|  limit           |           |         |          |  C     |  I             |
|  map             |  C        |  C      |          |        |                |
|  mapToXXX        |  C        |  C      |          |        |                |
|  max             |           |         |          |        |                |
|  min             |           |         |          |        |                |
|  noneMatch       |           |         |  C       |        |  I             |
|  peek            |           |         |          |        |                |
|  reduce          |           |         |          |        |                |
|  skip            |           |         |  C       |  I     |                |
|  sorted          |           |  I      |  I       |        |                |
|  toArray         |           |         |          |        |                |
  • C - 清除。
  • I - 注入。

【讨论】:

  • 我修复了其中一些标志;首先,forEachforEachOrdered 之间的根本区别在于forEach 不尊重每个合约的顺序,即使当前的实现可能不会在内部清除标志;这种设置/清除逻辑并不适合终端操作。此外,没有理由说明元素的顺序应该与count 操作相关,顺便说一句,这在Java 9 中可能是短路的。并且说sort 清除并同时注入SORTED 并没有多大意义,尤其是因为它很稳定……
  • 这个表只反映了实现的当前状态。 为什么 他们是否在终端操作中注入/清除标志是一个有趣的问题,也应该进行调查。感谢您的注释和编辑。
  • 嗯,有某种未排序终端操作到流管道的反向传播,可能对操作有影响,但是,它更复杂,例如Collector 可能是 UNORDERED,因此,collect 操作可能会将 无序 状态传播到流管道。对于其他特性,终端操作是否可以使用它会更有趣,例如toArray 将受益于 SIZED 特性,count 将在 Java 9 中...
【解决方案2】:

这听起来像是两个重复 - 因为您链接的两个答案实际上都解释了事情。我不知道mapfilter 是否应该明确说他们保留订单;他们不依赖任何先前状态或任何其他状态(这些是无状态操作),所以据我所知暗示他们保持秩序。如果他们不保留订单,我会反过来看 - 这应该在文档中明确提及;如果从操作名称上看不明显。 例如,Stream.generate 如果生成有序流,对我来说并不明显;因此在它的文档中是这样说的:

返回一个无限的顺序无序流,其中每个元素都由提供的供应商生成。

另一方面,sortedunordered 很明显 (IMO) 可以更改订单,至少当您将它们放入时 - 您明确表示您不关心订单。 unordered btw 不会故意进行任何随机化来满足这一点,您可以阅读更多 here

一般有两种顺序:加工顺序遇到顺序

您可以将遇到订单视为从左到右的处理(假设您有一个Listarray)。因此,如果您有一个不更改顺序的管道 - 从左到右,元素将被馈送到 Collector(或任何其他终端操作)。嗯,不是所有终端操作都是这样的。一个明显的区别是forEachforEachOrdered;或Collectors.toSet - 根本不需要保留初始订单。或者让我们以findAny 作为终端操作 - 显然您并不关心您想要哪个元素,那么为什么要首先按照确切的顺序输入findAny

另一方面,

处理顺序 没有定义的顺序 - 特别是对于并行处理明显可见。因此,即使您的管道是并行的(并且元素的处理绝对不保证任何顺序),它们仍将按顺序馈送到终端操作 - 如果该终端操作需要这样的顺序。

【讨论】:

  • 到目前为止,我想说的是,返回 unordered 流的 every 方法都是这样记录的。这也适用于unordered(),其文档声明它返回无序流,而sorted() 确实尊重遇到顺序,因为它将对有序流使用稳定的排序。
猜你喜欢
  • 2015-05-30
  • 2017-03-17
  • 2016-12-28
  • 1970-01-01
  • 2013-01-19
  • 1970-01-01
  • 1970-01-01
  • 2020-12-16
  • 2019-09-04
相关资源
最近更新 更多