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
如您所见,在平面映射之后,我应该得到从 1
到 9
的连续数字的有序流。我拆分了一次拆分器,所以它应该跳到某个中间位置。接下来我从中消耗一个元素并再次拆分它。之后我打印所有剩余的元素。我希望我将有几个来自流尾的连续元素(可能是零个元素,也可以)。然而我得到的是 5
和 6
,然后突然跳转到 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
提前拆分,这足以解决问题。
我正在使用流拆分器直接进行我正在编写的库中的低级操作。最近,当我进行流拆分器和交错 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
如您所见,在平面映射之后,我应该得到从 1
到 9
的连续数字的有序流。我拆分了一次拆分器,所以它应该跳到某个中间位置。接下来我从中消耗一个元素并再次拆分它。之后我打印所有剩余的元素。我希望我将有几个来自流尾的连续元素(可能是零个元素,也可以)。然而我得到的是 5
和 6
,然后突然跳转到 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
提前拆分,这足以解决问题。