在Java8中运行以下流示例:
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.html,流是顺序执行还是并行执行并不重要:
Except for operations identified as explicitly nondeterministic, such as findAny(), whether a stream executes sequentially or in parallel should not change the result of the computation.
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
实际上,似乎特别声明了,如果您的BinaryOperator是关联的,那么它应该适用于并行流。
正如其他答复者所指出的那样,除了不遵循减少的规则外,还有一种更简单的方法可以完成您的工作:stream.collect(joining(""))
从reduce的文档中:
The identity value must be an identity for the accumulator function. This means that for all t, accumulator.apply(identity, t) is equal to t.
在您的情况下,这是不正确的-"和" a"创建为" / a"。
我已经提取了累加器功能并添加了打印输出以显示发生了什么:
BinaryOperator 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语句以分别处理空字符串:
System.out.println(Stream
.of("a","b","c","d","e","f")
.parallel()
.reduce((s1, s2) -> s1.isEmpty()? s2 : s1 +"/" + s2)
);
正如Marko Topolnik所注意到的,由于累加器不一定是可交换函数,因此不需要检查s2。
太好了!因此,使用不使用身份的reduce也可以解决问题.reduce((s1, s2) -> s1 +"" + s2)。
最好使用条件表达式,恕我直言:(s1, s2) -> s1.isEmpty()? s2 : s2.isEmpty()? s1 : s1 +"" + s2
另外,由于累加器功能不需要交换,因此您实际上不需要检查s2是否为空。因此,(s1, s2) -> s1.isEmpty()? s2 : s1+""+s2就足够了。
或者他可以使用stream.collect(Collectors.joining("","",""))
@mk。这不是真的。第一步可以在3个线程中并行完成,分别创建a / b,c / d和e / f。然后,我们可以将前两个加入创建a / b / c / d,然后将其与e / f加入创建a / b / c / d / e / f。我在考虑身份是否会给我们带来任何好处,但是我找不到任何很好的解释-将功能与身份元素一起使用是多余的操作,它只会产生第二个元素。
因此,最大的问题是,为什么实现不能仅在内部将reduce(identity, accumulator)委托给reduce(accumulator).orElse(identity),因为" accumulator.apply(identity, t)等于t"的要求意味着在每个评估线程中执行whatever=accumulator.apply(identity, whatever)是胡说八道。
@Holger也许仅仅是因为它与FP纯粹主义者不合时宜:)
正是这一点:a + 0 != a。我没有阅读reduce文档中的注释。 +1
要添加其他答案,
您可能要使用Mutable减少,文档指定执行类似
String concatenated = strings.reduce("", String::concat)
会给出不良的性能结果。
We would get the desired result, and it would even work in parallel.
However, we might not be happy about the performance! Such an
implementation would do a great deal of string copying, and the run
time would be O(n^2) in the number of characters. A more performant
approach would be to accumulate the results into a StringBuilder,
which is a mutable container for accumulating strings. We can use the
same technique to parallelize mutable reduction as we do with ordinary
reduction.
因此,您应该改用StringBuilder。
对于刚开始使用lambda和流的人来说,花了很长时间才到达" AHA"时刻,直到我真正了解这里发生了什么。我会对此重新措辞,以使像我这样的新手流变得更轻松(至少我希望它真的得到了答复)。
所有这些都在reduce文档中指出:
标识值必须是累加器功能的标识。这意味着对于所有t,accumulator.apply(identity,t)等于t。
我们可以轻松证明代码的方式,关联性被破坏:
static private void isAssociative() {
BinaryOperator operator = (s1, s2) -> s1 +"/" + s2;
String result = operator.apply("","a");
System.out.println(result);
System.out.println(result.equals("a"));
}
一个空字符串与另一个字符串串联,应该真正产生第二个字符串。这不会发生,因此累加器(BinaryOperator)不具有关联性,因此在并行调用的情况下,reduce方法不能保证相同的结果。
操作员的关联性没有被破坏,唯一的问题是身份值。参见docs.oracle.com/javase/8/docs/api/java/util/stream/