Java8流顺序执行和并行执行会产生不同的结果吗?
在Java8中运行以下流示例:Java8流顺序执行和并行执行会产生不同的结果吗?,java,lambda,java-8,java-stream,Java,Lambda,Java 8,Java Stream,在Java8中运行以下流示例: System.out.println(Stream .of("a", "b", "c", "d", "e", "f") .reduce("", (s1, s2) -> s1 + "/" + s2) ); 收益率: /a/b/c/d/e/f 当然,这并不奇怪。 因为流是按顺序执行还是并行执行并不重要: 除了被明确标识为不确定的操作(如findAny())外,流是顺序执行还是并行执行都不应改变计算结果 AFAI
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.reduce("", (s1, s2) -> s1 + "/" + s2)
);
收益率:
/a/b/c/d/e/f
当然,这并不奇怪。
因为流是按顺序执行还是并行执行并不重要:
除了被明确标识为不确定的操作(如findAny())外,流是顺序执行还是并行执行都不应改变计算结果
AFAIKreduce()
是确定性的,而(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(collector.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
从reduce的文档中: 标识值必须是累加器函数的标识。这意味着对于所有t,累加器.apply(恒等式,t)等于t 这在您的案例中是不正确的-“”和“a”创建“/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)
);
您可以向函数中添加if语句以单独处理空字符串:
System.out.println(Stream
.of("a", "b", "c", "d", "e", "f")
.parallel()
.reduce((s1, s2) -> s1.isEmpty()? s2 : s1 + "/" + s2)
);
正如Marko Topolnik所注意到的,不需要检查
s2
,因为累加器不必是交换函数。要添加到其他答案中
您可能需要使用可变缩减,文档指定执行以下操作
String concatenated = strings.reduce("", String::concat)
将产生不好的性能结果
我们将得到预期的结果,甚至可以并行工作。
然而,我们可能对这次演出不满意!这样的
实现将执行大量字符串复制,并且运行
时间的字符数为O(n^2)。更有表现力的人
方法是将结果累积到StringBuilder中,
它是用于累积字符串的可变容器。我们可以使用
和我们使用普通方法一样,使用同样的技术来并行可变约简
减少
因此,您应该改用StringBuilder。对于刚开始使用lambdas和streams的人来说,花了相当长的时间才到达“啊哈”时刻,直到我真正理解这里发生了什么。对于像我这样的流媒体新手,我会重新表述一下,让它变得更容易一些(至少我希望它能得到真正的回答) 所有这些都在reduce文档中,其中说明: 标识值必须是累加器函数的标识。这意味着对于所有t,累加器.apply(标识,t)等于t。 我们可以很容易地证明,按照代码的方式,关联性被破坏了:
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"));
}
static private void isAssociative(){
二进制运算符=(s1,s2)->s1+“/”+s2;
字符串结果=运算符。应用(“,“a”);
系统输出打印项次(结果);
System.out.println(result.equals(“a”));
}
一个空字符串与另一个字符串连接,应该真正产生第二个字符串;这不会发生,因此累加器(BinaryOperator)不是关联的,因此在并行调用的情况下reduce方法不能保证相同的结果。事实上,如果您的
BinaryOperator
是,那么这应该适用于并行流。除了不遵循reduce规则之外,正如其他回答者所指出的,有一种更简单的方法来完成您正在做的事情:stream.collect(加入(“/”)
最好使用条件表达式,IMHO:(s1,s2)->s1.isEmpty()?s2:s2.isEmpty()?s1:s1+“/”+s2
此外,由于累加器函数不需要是可交换的,因此实际上不需要检查s2是否为空。那么,(s1,s2)->s1.isEmpty()?s2:s1+“/”+s2
就足够了。或者他可以使用stream.collect(收集器。加入(“/”,“/”,“))
所以最大的问题是,为什么实现不将reduce(identity,acculator)
委托给reduce(acculator)。或者将lse(identity)
内部作为“acculator.apply(identity,t)的要求
等于t
“意味着在每个计算线程中执行whatever=accumulator.apply(identity,whatever)
都是胡说八道。@Holger可能只是因为它不适合FP纯粹主义者:)运算符的关联性没有被破坏,唯一的问题是identity值。看见
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"));
}