首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >Java8 streams顺序和并行执行会产生不同的结果吗?

Java8 streams顺序和并行执行会产生不同的结果吗?
EN

Stack Overflow用户
提问于 2015-02-26 00:52:36
回答 3查看 3.3K关注 0票数 48

在Java8中运行以下流示例:

代码语言:javascript
复制
    System.out.println(Stream
        .of("a", "b", "c", "d", "e", "f")
        .reduce("", (s1, s2) -> s1 + "/" + s2)
    );

收益率:

代码语言:javascript
复制
/a/b/c/d/e/f

当然,这并不令人惊讶。由于有了http://docs.oracle.com/javase/8/docs/api/index.html?overview-summary.html,流是顺序执行还是并行执行都无关紧要:

除了被标识为显式不确定的操作(如findAny() )外,流是按顺序执行还是并行执行都不应更改计算结果。

AFAIK reduce()是确定性的,而(s1, s2) -> s1 + "/" + s2是关联的,因此添加parallel()应该会产生相同的结果:

代码语言:javascript
复制
    System.out.println(Stream
            .of("a", "b", "c", "d", "e", "f")
            .parallel()
            .reduce("", (s1, s2) -> s1 + "/" + s2)
    );

然而,在我的机器上的结果是:

代码语言:javascript
复制
/a//b//c//d//e//f

这是怎么回事?

顺便说一句:对于顺序和并行执行,使用(首选) .collect(Collectors.joining("/"))而不是reduce(...)会产生相同的结果a/b/c/d/e/f

JVM详细信息:

代码语言:javascript
复制
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
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2015-02-26 01:06:40

来自reduce的文档:

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

这在你的例子中不是真的- "“和"a”创建了"/a“。

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

代码语言:javascript
复制
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)
);

这是示例输出(每次运行都有所不同):

代码语言:javascript
复制
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语句添加到函数中,以单独处理空字符串:

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

正如Marko Topolnik注意到的,检查s2不是必需的,因为累加器不一定是交换函数。

票数 63
EN

Stack Overflow用户

发布于 2015-02-26 01:11:50

为了补充其他答案,

您可能想要使用可变还原,文档中指定这样做

代码语言:javascript
复制
String concatenated = strings.reduce("", String::concat)

会给出不好的性能结果。

我们会得到想要的结果,甚至可以并行工作。然而,我们可能对性能不满意!这样的实现将执行大量的字符串复制,并且运行时的字符数将为O(n^2)。一种性能更好的方法是将结果累积到一个StringBuilder,中,这是一个用于累积字符串的可变容器。我们可以使用与普通归约相同的技术来并行化可变归约。

因此,您应该使用StringBuilder。

票数 6
EN

Stack Overflow用户

发布于 2015-03-04 20:22:25

对于一个刚开始使用lambdas和stream的人来说,花了相当长的时间才达到"AHA“的时刻,直到我真正理解这里发生了什么。对于像我这样的流媒体新手来说,为了让它更简单(至少我希望它得到真正的回答),我将对此稍作修改。

这一切都是在reduce文档中声明的:

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

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

代码语言:javascript
复制
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")); 
}

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

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28724850

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档