当前位置: 首页 > 面试题库 >

终端操作无序的Stream.skip行为

吕嘉荣
2023-03-14
问题内容

让我们简单地输入数字1..20:

List<Integer> input = IntStream.rangeClosed(1, 20).boxed().collect(Collectors.toList());

现在,让我们创建一个并行流,结合unordered()skip()不同的方式和收集的结果:

System.out.println("skip-skip-unordered-toList: "
        + input.parallelStream().filter(x -> x > 0)
            .skip(1)
            .skip(1)
            .unordered()
            .collect(Collectors.toList()));
System.out.println("skip-unordered-skip-toList: "
        + input.parallelStream().filter(x -> x > 0)
            .skip(1)
            .unordered()
            .skip(1)
            .collect(Collectors.toList()));
System.out.println("unordered-skip-skip-toList: "
        + input.parallelStream().filter(x -> x > 0)
            .unordered()
            .skip(1)
            .skip(1)
            .collect(Collectors.toList()));

过滤步骤在这里基本上不执行任何操作,但是给流引擎增加了更多难度:现在它不知道输出的确切大小,因此一些优化被关闭了。我得到以下结果:

skip-skip-unordered-toList: [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
// absent values: 1, 2
skip-unordered-skip-toList: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 18, 19, 20]
// absent values: 1, 15
unordered-skip-skip-toList: [1, 2, 3, 4, 5, 6, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 19, 20]
// absent values: 7, 18

结果完全正确,一切正常。在第一种情况下,我要求跳过前两个元素,然后以没有特定的顺序收集到列表。在第二种情况下,我要求跳过第一个元素,然后变成无序并跳过另一个元素(我不在乎哪个元素)。在第三种情况下,我首先进入无序模式,然后跳过两个任意元素。

让我们跳过一个元素,以无序模式收集到自定义集合。我们的自定义收藏将是HashSet

System.out.println("skip-toCollection: "
        + input.parallelStream().filter(x -> x > 0)
        .skip(1)
        .unordered()
        .collect(Collectors.toCollection(HashSet::new)));

输出令人满意:

skip-toCollection: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
// 1 is skipped

因此,总的来说,我希望只要流被排序,就会skip()跳过第一个元素,否则会跳过任意​​一个。

但是,让我们使用等效的无序终端操作collect(Collectors.toSet())

System.out.println("skip-toSet: "
        + input.parallelStream().filter(x -> x > 0)
            .skip(1)
            .unordered()
            .collect(Collectors.toSet()));

现在的输出是:

skip-toSet: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20]
// 13 is skipped

相同的结果可以与任何其他无序终端操作来实现(如forEachfindAnyanyMatch等等)。unordered()在这种情况下,删除步骤不会更改任何内容。似乎虽然unordered()step正确地使流从当前操作开始无序,但无序的终端操作使整个流从开始就无序,尽管如果skip()使用它会影响结果。这似乎完全误导了我:我希望使用无序收集器与
在终端操作之前将 流转换为无序模式以及使用等效的有序收集器相同。

所以我的问题是:

  1. 这是故意的还是错误?
  2. 如果是,它是否记录在某处?我已经阅读了Stream.skip()文档:它没有说明无序的终端操作。另外,Characters.UNORDERED文档也不是很全面,并且没有说订购将对整个流程失去。最后,程序包摘要中的“ 订购”部分也不涉及这种情况。可能我缺少什么?
  3. 如果打算通过无序的终端操作使整个流无序,为什么unordered()仅从这一点开始,使step使其无序?我可以依靠这种行为吗?还是我很幸运,我的第一个测试效果很好?

问题答案:

回想一下,流标志(ORDERED,SORTED,SIZED,DISTINCT)的目标是使操作避免执行不必要的工作。涉及流标志的优化示例包括:

  • 如果我们知道流已经排序,sorted()则为空。
  • 如果我们知道流的大小,可以在中预先分配一个正确大小的数组toArray(),避免复制;
  • 如果我们知道输入没有有意义的相遇顺序,则无需采取额外的步骤来保留相遇顺序。

流水线的每个阶段都有一组流标志。中间操作可以注入,保留或清除流标志。例如,过滤保留排序性/独特性,但不保留大小性。映射保留大小,但不保留排序或唯一性。排序注入排序。对于中间操作,标志的处理非常简单,因为所有决策都是本地的。

对于终端操作,标志的处理更加微妙。ORDERED是与终端操作最相关的标志。如果终端op是UNDERDERED,则我们反向传播无序度。

我们为什么要做这个?好吧,请考虑以下管道:

set.stream()
   .sorted()
   .forEach(System.out::println);

由于forEach不限于按顺序操作,因此对列表进行排序的工作完全是浪费精力。因此,我们会对该信息进行反向传播(直到遇到短路操作,例如limit),以免失去优化机会。同样,我们可以distinct对无序流使用优化的实现。

这是故意的还是错误?

是的:)打算使用反向传播,因为它是有用的优化,不会产生错误的结果。但是,错误部分是我们正在传播过去的信息skip,我们不应该这样做。因此,UNORDERED标志的反向传播过于激进,这是一个错误。我们将发布一个错误。

如果是,它是否记录在某处?

它应该只是实现细节;如果正确实施,您将不会注意到(但您的流更快)。



 类似资料:
  • 受这个问题的启发,我开始研究有序流与无序流、并行流与顺序流以及终端操作,它们考虑的是相遇顺序,而终端操作则不考虑相遇顺序。 在链接问题的一个答案中,显示了一个类似于此的代码: 名单确实不同。列表甚至从一次运行更改到另一次运行,表明结果实际上是不确定的。 因此,我创建了另一个示例: 我希望看到类似的结果,因为流既并行又无序(可能是冗余的,因为它已经并行了)。但是,结果列表是有序的,即它等于源列表。

  • 一、常用终端操作 1.1、常用快捷键 CTRL+A: 移动光标至行首 CTRL+E: 移动光标至行尾 CTRL+U: 删除光标前所有字符 清除一行命令(输错一行命令的时候使用

  • 问题内容: 有人可以告诉我中间操作和终端操作有什么区别吗? 操作组合到管道中以处理流。所有操作都是中间操作或终端..means?。 问题答案: Stream支持几种操作,这些操作分为和操作。 此操作之间的区别在于,中间操作是惰性的,而终端操作不是。当您在流上调用中间操作时,该操作不会立即执行。仅在对该流调用终端操作时才执行该命令。在某种程度上,一旦调用了终端操作,便会存储并调用一次中间操作。您可以

  • 本文向大家介绍Java 8中的中间操作和终端操作之间的区别,包括了Java 8中的中间操作和终端操作之间的区别的使用技巧和注意事项,需要的朋友参考一下 在Java 8中引入了Stream,它仅用于处理数据组而不用于存储元素。它不修改实际的集合,它们仅根据流水线方法提供结果。 Stream api支持多种操作,并且操作分为两部分- 中间操作—这些操作用于管道化其他方法并转换为其他流。它们不会产生结果

  • 问题内容: 考虑以下代码: 终端操作(如)是否关闭已打开的基础文件? 请参阅Files.list的javadoc的相关部分: 返回的流封装了DirectoryStream。如果需要及时处理文件系统资源,则应使用try-with- resources构造来确保在流操作完成之后调用流的close方法。 如果不调用,那么在生成可维护代码时最好的替代方法是什么? 问题答案: 终端操作员不会自动关闭流。考虑