Stream.spliterator 对于并行流的奇怪行为

Strange behavior of Stream.spliterator for parallel streams

我正在使用流拆分器直接进行我正在编写的库中的低级操作。最近,当我进行流拆分器和交错 tryAdvance/trySplit 调用时,我发现了非常奇怪的行为。这是演示问题的简单代码:

import java.util.Arrays;
import java.util.Spliterator;

public class SpliteratorBug {
    public static void main(String[] args) {
        Integer[][] input = { { 1 }, { 2, 3 }, { 4, 5, 6 }, { 7, 8 }, { 9 } };
        Spliterator<Integer> spliterator = Arrays.stream(input).parallel()
                .flatMap(Arrays::stream).spliterator();
        spliterator.trySplit();
        spliterator.tryAdvance(s -> {});
        spliterator.trySplit();
        spliterator.forEachRemaining(System.out::println);
    }
}

输出为

5
6
9

如您所见,在平面映射之后,我应该得到从 19 的连续数字的有序流。我拆分了一次拆分器,所以它应该跳到某个中间位置。接下来我从中消耗一个元素并再次拆分它。之后我打印所有剩余的元素。我希望我将有几个来自流尾的连续元素(可能是零个元素,也可以)。然而我得到的是 56,然后突然跳转到 9.

我知道目前 JDK 拆分器不是这样使用的:它们总是在遍历之前拆分。然而官方documentation并没有明确禁止在tryAdvance.

之后调用trySplit

当我使用直接从集合、数组、生成的源等创建的拆分器时,从未观察到该问题。仅当拆分器是从具有中间 flatMap 的并行流创建时才会观察到该问题。

所以问题是:我是遇到了错误还是在某处明确禁止以这种方式使用拆分器?

AbstractWrappingSpliterator 和公司的来源可以看出,当你 tryAdvance 时,flatMap (4,5,6) 的输出得到缓冲,然后 4被消耗掉,在缓冲区中留下 (5,6)。然后 trySplit 正确地将 (7,8) 拆分为新的 Spliterator,在旧的 Spliterator 中留下 9,但缓冲的 (5,6) 留在旧的 Spliterator.

所以这对我来说像是一个错误。它应该将缓冲区交给新的 Spliterator 或 return null,如果缓冲区不为空则拒绝拆分。

来自 Spliterator.trySplit() 的文档:

This method may return null for any reason, including emptiness, inability to split after traversal has commenced, data structure constraints, and efficiency considerations.

(强调我的)

因此文档明确提到了在开始遍历后尝试拆分的可能性,并建议无法处理此问题的拆分器可能 return null.

因此对于有序拆分器,观察到的行为应该被视为一个错误 。通常,trySplit() 必须 return 一个 prefix spliterator,换句话说,必须将所有关于下一个项目的中间状态交给新的spliterator , 是 Spliterator API 的一个特性,可能会导致错误。我把这个问题作为检查我自己的拆分器实现的动机,发现了一个类似的错误…

此行为被官方认定为错误(请参阅 JDK-8148838), fixed by me and pushed into JDK-9 trunk (see changeset). The sad thing is that my initial patch actually fixed the splitting after flatMap (see webrev),但此补丁已被拒绝,因为这种情况(在 tryAdvance() 之后使用 trySplit())被认为是不常见且不鼓励的.目前接受的解决方案是完全禁用 WrappingSpliterator 提前拆分,这足以解决问题。