Итератор() на параллельном потоке гарантирует порядок поиска?
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
в упорядоченном потоке или последовательности (например, реализация списка) должен производить элементов в этом порядке при перечислении.
Поэтому не имеет значения, выполняется ли код последовательно или параллельно, если у нас есть упорядоченный источник, промежуточные операции (операции) и операция терминала, которая соблюдает этот порядок, тогда порядок должен поддерживаться.