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

2022-08-31 22:32:45

在 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 流是按顺序执行还是并行执行并不重要:

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

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

    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

答案 1

从 Reduce 的文档:

标识值必须是累加器函数的标识。这意味着对于所有 t,accumulator.apply(identity, 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)
);

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

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


答案 2

要添加到其他答案,

您可能希望使用可变减少,文档指定执行类似操作

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

会给出性能不好的结果。

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

所以你应该改用StringBuilder。


推荐