Java8stream顺序和并行执行产生不同的结果?

在Java8中运行以下stream示例:

System.out.println(Stream .of("a", "b", "c", "d", "e", "f") .reduce("", (s1, s2) -> s1 + "/" + s2) ); 

收益率:

 /a/b/c/d/e/f 

当然,这并不奇怪。 由于http://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.htmlstream是顺序执行还是并行执行都无关紧要:

除了标识为明确不确定的操作(如findAny())之外,顺序执行还是并行执行都不应改变计算结果。

AFAIK reduce()是确定性的,并且(s1, s2) -> s1 + "/" + s2是关联的,所以添加parallel()应该产生相同的结果:

  System.out.println(Stream .of("a", "b", "c", "d", "e", "f") .parallel() .reduce("", (s1, s2) -> s1 + "/" + s2) ); 

但是我的机器上的结果是:

 /a//b//c//d//e//f 

这里有什么问题?

顺便说一句:使用(首选的) .collect(Collectors.joining("/"))而不是reduce(...)产生相同的结果a/b/c/d/e/f

JVM详细信息:

 java.specification.version: 1.8 java.version: 1.8.0_31 java.vm.version: 25.31-b07 java.runtime.version: 1.8.0_31-b13 

从减less的文件:

身份值必须是累加器函数的标识。 这意味着对于所有的t,accumulator.apply(identity,t)等于t。

你的情况不是这样 – “”和“a”创build“/ a”。

我已经提取了累加器函数,并添加了一个打印输出来显示发生了什么:

 BinaryOperator<String> accumulator = (s1, s2) -> { System.out.println("joining \"" + s1 + "\" and \"" + s2 + "\""); return s1 + "/" + s2; }; System.out.println(Stream .of("a", "b", "c", "d", "e", "f") .parallel() .reduce("", accumulator) ); 

这是示例输出(它在不同运行之间):

 joining "" and "d" joining "" and "f" joining "" and "b" joining "" and "a" joining "" and "c" joining "" and "e" joining "/b" and "/c" joining "/e" and "/f" joining "/a" and "/b//c" joining "/d" and "/e//f" joining "/a//b//c" and "/d//e//f" /a//b//c//d//e//f 

你可以添加一个if语句到你的函数来分别处理空string:

 System.out.println(Stream .of("a", "b", "c", "d", "e", "f") .parallel() .reduce((s1, s2) -> s1.isEmpty()? s2 : s1 + "/" + s2) ); 

正如Marko Topolnik所注意到的那样,检查s2是不需要的,因为累加器不必是交换function。

要添加到其他答案,

您可能想要使用可变减less ,文档指定做类似的事情

 String concatenated = strings.reduce("", String::concat) 

会给糟糕的performance结果。

我们会得到期望的结果,甚至可以并行工作。 但是,我们可能不会对表演感到高兴! 这样的实现会进行大量的string复制,运行时间将会是字符数量的O(n ^ 2)。 更高效的方法是将结果累积到一个StringBuilder中 ,这是一个用于累加string的可变容器。 我们可以使用相同的技术来平行化可变的缩减,就像我们用普通的缩减一样。

所以你应该使用一个StringBuilder来代替。

对于刚开始使用lambda和stream的人来说,花了相当长的时间才能到达“AHA”时刻,直到我真正理解了这里发生的事情。 我会稍微改写一下,以便让我更容易(至less我希望它真的被回答了)像一个像我这样的stream新手。

这一切都在减less文件,指出:

标识值必须是累加器函数的标识。 这意味着对于所有的t,accumulator.apply(identity,t)等于t。

我们可以很容易地certificate代码的方式,关联性被打破:

 static private void isAssociative() { BinaryOperator<String> operator = (s1, s2) -> s1 + "/" + s2; String result = operator.apply("", "a"); System.out.println(result); System.out.println(result.equals("a")); } 

一个空string连接到另一个string,应该真的产生第二个string; 这不会发生,因此累加器(BinaryOperator)不是关联的,因此reduce方法在并行调用的情况下不能保证相同的结果。