【发布时间】:2015-06-15 10:50:03
【问题描述】:
我已经阅读了 this 和 this 的问题,但仍然怀疑观察到的 Stream.skip 的行为是否是 JDK 作者的意图。
让我们简单地输入数字 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
任何其他无序终端操作(如forEach、findAny、anyMatch 等)都可以实现相同的结果。在这种情况下删除 unordered() 步骤不会改变任何事情。似乎虽然unordered() 步骤正确地使流从当前操作开始无序,但无序的终端操作使整个流从一开始就无序,尽管如果使用skip() 会影响结果。这对我来说似乎完全误导了我:我希望使用无序收集器与将流转换为无序模式在终端操作之前并使用等效的有序收集器相同。
所以我的问题是:
- 这种行为是有意的还是一个错误?
- 如果是,是否记录在某处?我已阅读 Stream.skip() 文档:它没有说明无序终端操作。 Characteristics.UNORDERED 文档也不是很容易理解,也没有说整个流的排序都会丢失。最后,包摘要中的Ordering 部分也不涵盖这种情况。可能我错过了什么?
- 如果打算无序的终端操作使整个流无序,为什么
unordered()步骤仅从这一点开始使其无序?我可以依靠这种行为吗?还是我很幸运,我的第一个测试运行良好?
【问题讨论】:
-
但这就是问题 - 没有“之前”。前面的所有操作都是中间操作,只有在你执行了终端操作时才会发生流缩减 - 在这种情况下为收集。
-
正如我已经说过的there,如果行为一致,则更容易理解。这仍然允许故意在该问题中显示的行为,但是我们可能会将订单保留得太频繁的事实视为错误。你知道,
sorted().forEach()应该不排序。 -
您的初始代码行是否缺少
boxed()调用?没有boxed(),我不能像那样collect()。 -
@Thomas,谢谢,
boxed()已添加。.parallelStream().filter(x -> x > 0)是必要的,因为我想揭示问题,而不是消除它们:-) 当然这是一个人为的简化示例。实际上,如果您使用例如bufferedReader.lines().skip(1).parallel().forEach(...),则可能会出现此类问题。请参阅链接的问题。 -
@FedericoPeraltaSchaffner,如果你解析一些带有标题行的文本文件并进行高 Q 处理,那么
lines.stream().skip(1).parallel().blahblah可能对你很有效。
标签: java parallel-processing java-8 java-stream collectors