Итератор() на параллельном потоке гарантирует порядок поиска?

Stream.of(a, b, c).parallel().map(Object::toString).iterator();

Является ли возвращенный итератор гарантированным, чтобы обеспечить значения 2, 3, 4 в этом порядке?

Я знаю, что toArray() и collect() гарантирует коллекции со значениями в правильном порядке. Кроме того, я не спрашиваю, как создать поток из итератора.

Ответ 1

Это надзор в спецификации. Если поток имеет определенный порядок встреч, целью было то, что его Итератор создает элементы в порядке встречи. Если поток не имеет определенного порядка встреч, Итератор, конечно, будет создавать элементы в некотором порядке, но этот порядок не будет определен.

Я зарегистрировал ошибку JDK-8194952, чтобы отслеживать изменение спецификации.

Похоже, что другие проскальзывали достаточную реализацию, чтобы показать, что она действительно создаст элементы в порядке встречи. Кроме того, наши потоковые тесты полагаются на это свойство. Например, тест для коллектора toList утверждает, что элементы в списке присутствуют в том же порядке, в каком они получены из Итератора потока. Таким образом, вам, вероятно, безопасно полагаться на это поведение, хотя оно формально не указано (пока).

Ответ 2

Stream.of method, используемый для создания потока из других несвязанных значений, возвращает последовательный упорядоченный поток.

Возвращает последовательный упорядоченный поток, чьи элементы являются указанными значениями.

В соответствии с пакет Javadocs для java.util.stream раздел "Побочные эффекты":

IntStream.range(0,5).parallel().map(x -> x*2).toArray() должен производить [0, 2, 4, 6, 8]

Это означает, что parallel() и map() сохраняют, является ли поток последовательным/упорядоченным.

Я проследил реализацию Stream, которую Stream.of создает в класс под названием ReferencePipeline.

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

Этот метод iterator() реализует Spliterator.iterator(), код которого адаптируется к интерфейсу Iterator, просто полагаясь на Spliterator tryAdvance и не изменяет никаких характеристик потока:

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 создает "последовательный упорядоченный поток", и ни одна из операций, которые вы используете выше: parallel, map или Iterator изменить характеристики. Фактически, Iterator использует базовый поток Spliterator для итерации по элементам потока.

Ответ 3

Ближайшим к гарантии, которую я нашел до сих пор, является следующий оператор в package documentaion для java.util.stream:

За исключением операций, идентифицированных как явно недетерминированные, например findAny(), независимо от того, выполняется ли поток последовательно или параллельно, не следует изменять результат вычисления.

Возможно, iterator(), производящий Iterator итерацию в другом порядке, будет "изменением результата", так же, как создание элементов, содержащих List в другом порядке, будет для collect().

Ответ 4

Да, будет. И это потому, что терминальные операции, если в документации не указано иначе (forEach - это явно указывает, что это не детерминировано vs forEachOrdered), они сохраняют порядок встреч. И ваш Stream.of возвращает упорядоченный поток; который не разбит нигде (например, через unordered или sorted/distinct)

Ответ 5

Учитывая тот факт, что возвращается Stream.of, упорядоченный поток, указанный в документации, и ни одна из промежуточных операций, которые вы указали измените это поведение, поэтому мы можем сказать, что возвращенный iterator гарантированно обеспечит значения 2, 3, 4 в том порядке, в котором они перечислены, поскольку вызов операции терминала iterator в упорядоченном потоке или последовательности (например, реализация списка) должен производить элементов в этом порядке при перечислении.

Поэтому не имеет значения, выполняется ли код последовательно или параллельно, если у нас есть упорядоченный источник, промежуточные операции (операции) и операция терминала, которая соблюдает этот порядок, тогда порядок должен поддерживаться.