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方法在并行调用的情况下不能保证相同的结果。