【问题标题】:iterator() on parallel stream guarantee encounter order?并行流上的迭代器()保证遇到顺序?
【发布时间】:2018-06-20 02:23:52
【问题描述】:
Stream.of(a, b, c).parallel().map(Object::toString).iterator();

返回的迭代器是否保证按顺序提供值abc

我知道toArray()collect() 保证集合的值顺序正确。另外,我不是在问如何从迭代器中创建流。

【问题讨论】:

标签: java java-stream


【解决方案1】:

这是规范中的疏忽。如果流具有定义的遇到顺序,则意图是其迭代器按遇到顺序生成元素。如果流没有定义的遇到顺序,迭代器当然会以 some 顺序生成元素,但不会定义该顺序。

我已提交错误JDK-8194952 以跟踪对规范的更改。

看起来其他人已经通过了足够多的实现来表明它确实会按照遇到的顺序生成元素。此外,我们的流测试依赖于这个属性。例如,toList 收集器的测试断言列表中的元素与从流的迭代器中获取的顺序相同。因此,即使尚未正式指定(尚未),您也可以放心地依赖此行为。

【讨论】:

  • 是的,其他操作也需要澄清,即即使mapfilter 也没有指定任何内容。或者,除非另有说明,否则只需添加一个明确的声明,即操作维护相遇顺序(如果有的话)。如果我们在它上面,max()min() 返回第一个,以防有序流或例如reduce((a,b)->a) 返回第一个元素,分别。 reduce((a,b)->b) 返回最后一个元素也只是隐式的。如果只有一个输入是无序的,则 Stream.concat 是无序的,例如concat(range(0, 10), empty()),很明确,但是一个可怕的决定……
  • 通过测试 .iterator() 似乎将并行流变成了顺序流,这使得这个问题没有意义。
【解决方案2】:

Stream.of method,用于从其他不相关的值创建流,返回一个顺序的、有序的流。

返回一个顺序有序的流,其元素是指定的值。

根据package Javadocs for java.util.stream副作用部分:

IntStream.range(0,5).parallel().map(x -> x*2).toArray() 必须产生[0, 2, 4, 6, 8]

这意味着parallel()map() 保留流是否是顺序/有序的。

我已经追踪了Stream.of 创建的Stream 的实现到一个名为ReferencePipeline 的类。

@Override
public final Iterator<P_OUT> iterator() {
    return Spliterators.iterator(spliterator());
}

该实现的iterator() 方法遵循Spliterator.iterator(),其代码仅依赖SpliteratortryAdvance 方法即可适应Iterator 接口,并且不会更改任何流特征:

public static<T> Iterator<T> iterator(Spliterator<? extends T> 
    spliterator) {
    Objects.requireNonNull(spliterator);
    class Adapter implements Iterator<T>, Consumer<T> {
        boolean valueReady = false;
        T nextElement;

        @Override
        public void accept(T t) {
            valueReady = true;
            nextElement = t;
        }

        @Override
        public boolean hasNext() {
            if (!valueReady)
                spliterator.tryAdvance(this);
            return valueReady;
        }

        @Override
        public T next() {
            if (!valueReady && !hasNext())
                throw new NoSuchElementException();
            else {
                valueReady = false;
                return nextElement;
            }
        }
    }

    return new Adapter();
}

总之,是的,顺序是有保证的,因为Stream.of 创建了一个“顺序有序流”,并且您在上面使用的任何操作:parallelmapiterator 都不会改变特性。事实上,iterator 使用底层 Stream Spliterator 来迭代流元素。

【讨论】:

  • 你不能通过展示实现来证明规范。
  • 我仍然认为 Javadoc 在 OP 案例中不“保证”顺序。 Javadoc 中没有任何地方说iterator 方法将始终尊重遇到顺序。
  • 代码证明Iterator并没有改变Spliterator的顺序,所以你只是把问题改成“并行流上的spliterator()是否保证遇到顺序?”
【解决方案3】:

到目前为止,我发现的最接近保证的是package documentaion for java.util.stream 中的以下声明:

除了明确识别为非确定性的操作(例如 findAny())外,流是顺序执行还是并行执行不应改变计算结果。

可以说,iterator() 产生一个以不同顺序迭代的 Iterator 将是“结果的变化”,就像产生一个包含以不同顺序的元素的 List 对于 collect() 一样。

【讨论】:

    【解决方案4】:

    是的,它会的。这是因为终端操作,除非文档中另有说明(例如forEach - 明确指定这是非确定性与forEachOrdered),它们确实保留了遇到顺序。而你的Stream.of 确实返回了一个 ordered 流;在任何地方都没有损坏的订单(例如通过unordered,或sorted/distinct

    【讨论】:

    • 除非另有说明,否则您是否有断言终端操作尊重遇到顺序的来源?
    • @shmosel 我记得读过这个话题,如果我没记错的话是 Stuart Marks(或 Holger?)说的。找到后会更新
    • @shmosel 没有明确声明这个策略,这不是一个好的情况,但是,维护顺序没有明确声明是一个公正的事实,即即使是像@这样的简单操作987654326@或map没有关于维持订单的声明。即使对于reducecollect,您也只能从函数必须是关联的但不需要是可交换的这一事实中推导出订单的维护。
    • @shmosel 查看我刚刚发布的答案。 TLDR:规范错误。我想我没有在其他任何地方讨论过这个问题,但我可能已经忘记了。
    • @Holger 见我上面的评论。
    【解决方案5】:

    鉴于Stream.of 返回的事实,文档中所述的有序流以及您展示的任何中间操作都不会改变这种行为,因此,我们可以说返回的iterator 保证提供值2, 3, 4 在枚举时按顺序排列,因为在有序流或序列(例如List 实现)上调用iterator 终端操作应该在枚举结束时按该顺序生成元素。

    因此,代码是顺序执行还是并行执行都无关紧要,只要我们有一个有序的源、中间操作和终端操作遵守这个顺序,那么就应该保持这个顺序。

    【讨论】:

    • 你说iterator()尊重相遇顺序的依据是什么?
    • @shmosel 并不是说​​这适用于所有情况。但是对于提供的示例,它应该尊重遇到的顺序,因为流仍然是有序的。
    • 这是一个重言式。仅仅因为流是有序的并不意味着每个操作都会尊重它的顺序。 forEach() 没有。许多收藏家没有。
    • @shmosel 对,那是 forEach... 但如果流或源具有指定的顺序(保留顺序),则 Iterator 应按该顺序检索元素枚举。
    • @shmosel 还请注意,如我的回答中所述,如果源和中间操作以及终端操作遵守遇到顺序,那么流是否并行执行都无关紧要我们应该保持这种遭遇秩序。所以我不只是说“仅仅因为一个流是有序的”。至于迭代器,枚举时检索元素的顺序取决于我们正在处理的集合或流的类型。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-18
    • 2015-05-19
    • 2011-11-30
    相关资源
    最近更新 更多