10

我将流分离器直接用于我正在编写的库中的低级操作。最近,当我使用流分离器和交错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

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

我知道目前在 JDK 中没有以这种方式使用拆分器:它们总是在遍历之前拆分。但是官方文档并没有明确禁止调用trySplitafter tryAdvance

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

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

4

3 回答 3

6

从以下文档Spliterator.trySplit()

此方法可能null因任何原因返回,包括空虚、遍历开始后无法拆分、数据结构约束和效率考虑。

(强调我的)

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

因此,对于有序拆分器,观察到的行为应视为Misha 描述的错误。通常,trySplit()必须返回前缀拆分器的事实,换句话说,必须将有关下一个项目的所有中间状态移交给新的拆分器,这是SpliteratorAPI 的一个特性,它可能会导致错误。我把这个问题作为检查我自己的拆分器实现的动机,发现了一个类似的错误……</p>

于 2015-07-01T10:35:46.867 回答
5

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

所以这对我来说似乎是一个错误。如果缓冲区不为空,它应该要么将缓冲区交给新缓冲区,Spliterator要么返回null并拒绝拆分。

于 2015-07-01T06:55:19.983 回答
2

这种行为被官方认定为错误(参见JDK-8148838),由我修复并推送到 JDK-9 主干(参见变更集)。可悲的是,我最初的补丁实际上修复了分裂之后flatMap(参见webrev),但是这个补丁被拒绝了,因为这种情况(使用trySplit()after tryAdvance())被认为是不常见的并且不鼓励。当前接受的解决方案是WrappingSpliterator在提前之后禁用拆分,这足以解决问题。

于 2016-02-08T16:50:03.030 回答