Каков порядок, в котором потоковые операции применяются к элементам списка?

Предположим, что у нас есть стандартная цепочка методов операций потока:

Arrays.asList("a", "bc", "def").stream()
  .filter(e -> e.length() != 2)
  .map(e -> e.length())
  .forEach(e -> System.out.println(e));

Существуют ли какие-либо гарантии в JLS относительно порядка, в котором потоковые операции применяются к элементам списка?

Например, гарантируется ли это:

  • Применение предиката фильтра к "bc" не произойдет, прежде чем применять предикат фильтра к "a"?
  • Применение функции сопоставления к "def" не произойдет, прежде чем применять функцию сопоставления к "a"?
  • 1 будет напечатано до 3?

Примечание. Здесь я говорю конкретно о stream(), not parallelStream(), где предполагается, что операции, такие как сопоставление и фильтрация, выполняются параллельно.

Ответ 1

Все, что вы хотите знать, можно найти в java.util.stream JavaDoc.

Заказ

Потоки могут иметь или не иметь определенного порядка встреч. Независимо от того, поток имеет порядок встреч, зависит от источника и промежуточные операции. Определенные источники потока (такие как List или массивы) являются внутренне упорядоченными, тогда как другие (такие как HashSet) не. Некоторые промежуточные операции, такие как sorted(), могут порядок встречи в неупорядоченном потоке, а другие могут сделать упорядоченный поток неупорядоченным, например BaseStream.unordered(). Кроме того, некоторые операции терминала могут игнорировать порядок встреч, например Foreach().

Если поток упорядочен, большинство операций ограничено для работы элементы в порядке их встреч;, если источник потока является Список, содержащий [1, 2, 3], то результат выполнения отображения (x → x * 2) должен быть [2, 4, 6]. Однако, если источник не имеет определенного контакта порядок, то любая перестановка значений [2, 4, 6] будет действительной результат.

Для последовательных потоков наличие или отсутствие порядка встречи не влияет на производительность, а только на детерминизм. Если поток упорядочен, повторное выполнение идентичных поточных трубопроводов на одинаковом источник даст идентичный результат; если он не упорядочен, повторное выполнение может привести к различным результатам.

Для параллельных потоков расслабление ограничения порядка может иногда обеспечивают более эффективное выполнение. Некоторые совокупные операции, такие как фильтрация дубликатов (отдельные()) или сгруппированные сокращения (Collectors.groupingBy()) могут быть реализованы более эффективно, если упорядочение элементов не имеет значения. Аналогичным образом, операции, которые внутренне привязанный к порядку встречи, такой как limit(), может потребоваться буферизации для обеспечения надлежащего упорядочения, подрывая преимущества parallelism. В тех случаях, когда поток имеет порядок встреч, но пользователь не особо заботится о том, чтобы встречался, явно де-упорядочение потока с неупорядоченным() может улучшить параллельность производительность для некоторых операций с состоянием или терминалом. Однако большинство поточные трубопроводы, такие как пример "суммы веса блоков" выше, все еще эффективно распараллеливаются даже при ограничениях порядка.

Ответ 2

Существуют ли какие-либо гарантии в JLS относительно порядка, в котором потоковые операции применяются к элементам списка?

Библиотека Streams не распространяется на JLS. Вам нужно будет прочитать Javadoc для библиотеки.

Потоки также поддерживают параллельный поток, и порядок обработки данных зависит от реализаций.

Применение предиката фильтра к "bc" не произойдет, прежде чем применять предикат фильтра к "a"?

Было бы разумно предположить, что это было бы, но вы не можете гарантировать это, и не должны писать код, который требует этой гарантии, иначе вы не сможете его параллелизировать позже.

применение функции отображения к "def" не произойдет, прежде чем применять функцию сопоставления к "a"?

Безопасно предположить, что это происходит, но вы не должны писать код, который требует его.

Ответ 3

Нет гарантии того, что элементы списка передаются в предикат lambdas. Потоковая документация дает гарантии относительно вывода потоков, в том числе порядок встречи; он не дает гарантий относительно деталей реализации, таких как порядок, в котором применяются предикаты фильтра.

Следовательно, документация не предотвращает filter, скажем, чтение нескольких элементов, выполнение предиката на них в обратном порядке, а затем отправку элементов, передающих предикат, в выходной поток потока в том порядке, в котором они пришел. Я не знаю, почему filter() будет делать что-то подобное, но это не нарушит никаких гарантий, сделанных в документации.

Вы можете сделать довольно сильный вывод из документации, которая filter() будет вызывать предикат для элементов в том порядке, в котором их предоставляет коллекция, потому что вы передаете результат вызова stream() в списке, который вызывает Collection.stream() и, согласно документации Java, гарантирует, что Stream<T>, созданный таким образом, является последовательным:

Возвращает последовательный Stream с этой коллекцией в качестве источника.

Кроме того, filter() является апатридом:

Операции без состояния, такие как filter и map, не сохраняют состояние из ранее увиденного элемента при обработке нового элемента - каждый элемент может обрабатываться независимо от операций над другими элементами.

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

Я говорю здесь конкретно о stream(), а не parallelStream()

Обратите внимание, что Stream<T> может быть неупорядоченным без параллелизма. Например, вызывая unordered() на stream(), результат становится неупорядоченным, но не параллельным.

Ответ 4

Если поток создается из списка, гарантируется, что собранный результат будет упорядочен так же, как и исходный список, как указано в документации:

Заказ

Если поток упорядочен, большинство операций ограничено для работы с элементами в их порядке встречи; если источником потока является Список, содержащий [1, 2, 3], то результатом выполнения отображения (x → x * 2) должно быть [2, 4, 6]. Однако, если источник не имеет определенного порядка регистрации, то любая перестановка значений [2, 4, 6] будет действительным результатом.

Чтобы идти дальше, нет никакой гарантии относительно порядка выполнения выполнения map.

На той же странице документа (в разделе Побочные эффекты):

Побочные эффекты

Если поведенческие параметры имеют побочные эффекты, если не указано явно, нет никаких гарантий относительно видимости этих побочных эффектов для других потоков и нет никаких гарантий того, что различные операции над "одним и тем же" элементом внутри тот же потоковый конвейер выполняется в одном потоке. Кроме того, упорядочение этих эффектов может быть неожиданным. Даже когда конвейер ограничен для получения результата, который согласуется с порядком встречи источника потока (например, IntStream.range(0,5).parallel(). Map (x → x * 2).toArray( ) должен произвести [0, 2, 4, 6, 8]), никаких гарантий относительно порядка, в котором функция сопоставления не применяется к отдельным элементам, или в каком потоке не выполняется какой-либо поведенческий параметр для данного элемента.

На практике для упорядоченного последовательного потока есть вероятность, что операции потока будут выполняться по порядку, но нет гарантии.

Ответ 5

Есть ли в JLS гарантии относительно порядка, в котором потоковые операции применяются к элементам списка?

Цитата из Раздел заказа в потоках javadocs

  • Потоки могут иметь или не иметь определенный порядок встреч. Так или иначе поток имеет порядок встреч, зависит от источника и промежуточные операции.


Применение предиката фильтра к "bc" не произойдет раньше применяя предикат фильтра к "a" ?

Как указано выше, потоки могут иметь или не иметь определенного порядка. Но в вашем примере, так как это список, тот же раздел "Заказ" в Stream javadocs продолжает говорить, что

  • Если поток упорядочен, большинство операций ограничено для работы элементы в порядке их встречи; если источник потока является Список, содержащий [1, 2, 3], то результат выполнения отображения (x → x * 2) должен быть [2, 4, 6].

    Применение приведенного выше утверждения к вашему примеру - я считаю, предикат фильтра получит элементы в порядке, определенном в Списке.


Или, применяя функцию отображения к "def", не произойдет, прежде чем применять функцию сопоставления к "a" ?

Для этого я хотел бы обратиться к разделу операций Stream Операции Stream в потоках, в котором говорится:

  • Операции без состояния, такие как фильтр и карта, не сохраняют состояние из ранее увиденный элемент при обработке нового элемента

    Так как map() не сохраняет состояние, я считаю, можно с уверенностью предположить, что "def" не будет обрабатываться до "a" в вашем примере.


1 будет напечатано до 3?

Хотя это может быть маловероятно с последовательными потоками (например, List), но не гарантируется, так как раздел "Заказ" в Stream javadocs указывает, что

  • некоторые операции терминала могут игнорировать порядок встреч, например Foreach().