stream parallel skip - 链接流方法的顺序是否有所不同?

时间:2016-06-28 15:31:58

标签: java java-8 java-stream

stream.parallel().skip(1) 

vs

stream.skip(1).parallel() 

这是关于Java 8流的 这两个都跳过了第一行/条目吗?

示例是这样的:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.concurrent.atomic.AtomicLong;

public class Test010 {

    public static void main(String[] args) {
        String message = 
        "a,b,c\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n1,2,3\n4,5,6\n7,8,9\n";

        try(BufferedReader br = new BufferedReader(new StringReader(message))){

            AtomicLong cnt = new AtomicLong(1);

            br.lines().parallel().skip(1).forEach(
                s -> {
                    System.out.println(cnt.getAndIncrement() + "->" + s);
                }
            );

        }catch (IOException e) {
            e.printStackTrace();
        }

    }

}

今天早些时候,我有时会得到标题行" a,b,c"在lambda表达式中。这是一个惊喜,因为我期待已经跳过它。现在我无法让这个例子工作,即我无法获得lambda表达式中的标题行。所以我现在很困惑,也许其他东西正在影响这种行为。当然这只是一个例子。在现实世界中,正在从CSV文件中读取消息。该消息是该CSV文件的完整内容。

2 个答案:

答案 0 :(得分:3)

是的,但skip(n)较慢,因为并行流的n较大。

以下是来自skip()的API说明:

  

虽然skip()通常是顺序流管道上的廉价操作,但在有序并行管道上可能非常昂贵,特别是对于n的大值,因为skip(n)被限制为跳过不仅仅是n元素,还有遇到订单中的第一个n 元素。如果您的情境的语义允许,使用无序流源(例如generate(Supplier))或使用BaseStream.unordered()删除排序约束可能会导致并行管道中skip()的显着加速。如果需要与遭遇顺序保持一致,并且您在并行管道中使用skip()时性能或内存利用率较低,则切换到使用BaseStream.sequential()执行顺序执行可能会提高性能。

基本上,如果您希望使用skip()获得更好的性能,请不要使用parellel流,或使用无序流。

至于它似乎不适用于并行流,或许您实际上看到元素不再被排序?例如,此代码的输出:

Stream.of("Hello", "How", "Are", "You?")
    .parallel()
    .skip(1)
    .forEach(System.out::println);

  

是否
  你?
  如何

Ideone Demo

这很好,因为forEach不会在并行流中强制执行遭遇顺序。如果您希望它强制执行遭遇顺序,请使用顺序流(并且可能使用forEachOrdered以便您的意图明显)。

Stream.of("Hello", "How", "Are", "You?")
    .skip(1)
    .forEachOrdered(System.out::println);
  

如何
  是
  你呢?

答案 1 :(得分:3)

你实际上有两个问题,第一个是在编写stream.parallel().skip(1)还是stream.skip(1).parallel()时是否有所不同,第二个问题是其中一个或两个是否总是跳过第一个元素。另请参阅“loaded question”

第一个答案是没有区别,因为指定.sequential().parallel()执行策略会影响整个Stream管道,无论您将它放在调用链中的哪个位置 - 当然,除非你指定了多个矛盾的政策,在这种情况下,最后一个政策获胜。

因此,在任何一种情况下,您都在请求并行执行,这可能会影响skip操作的结果,这是第二个问题的主题。

答案并非那么简单。如果Stream首先没有定义的遭遇顺序,则可能会跳过任意​​元素,这是因为没有“first”元素,即使在迭代时可能存在首先遇到的元素源。

如果你有一个有序的流,skip(1) 跳过第一个元素,但这只是最近才制定的。正如“Stream.skip behavior with unordered terminal operation”中所讨论的,链接无序终端操作会对早期实现中的skip操作产生影响,并且在“Is this a bug in Files.lines(), or am I misunderstanding something about parallel streams?”中可以看到这是否有意甚至是有意的,这恰好接近你的代码;显然,跳过第一行是一种常见的情况。

最后一句话是,早期JRE的行为是一个错误,有序流上的skip(1)应该跳过第一个元素,即使并行执行流管道并且终端操作是无序的。 associated bug report将jdk1.8.0_60命名为第一个固定版本,我可以验证。因此,如果您使用较旧的实现,那么可能在使用.parallel()和无序.forEach(…)终端操作时会遇到跳过不同元素的Stream。如果实现偶尔会跳过预期的元素,这就是多线程的不可预测性,这并不矛盾。

所以答案仍然是stream.parallel().skip(1)stream.skip(1).parallel()具有相同的行为,即使在早期版本中使用时也是如此,因为两者在与{{1}等无序终端操作一起使用时同样不可预测}}。他们应该总是跳过带有序Streams的第一个元素,当与forEach或更新版本一起使用时,它们会这样做。