Потоки Java: какие операции сохраняют порядок

TL; DR; Я ищу место, где я могу найти, есть ли какая-то промежуточная или терминальная операция. Где я могу найти такую ​​документацию?

Фон

документация пакета говорит:

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

Что повторяется в qaru.site/info/93156/...

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

Это хорошо и хорошо, но на какую документацию я должен смотреть? документация по упаковке упоминает в примере, что map гарантирует упорядочение, но у него нет исчерпывающего списка. javadoc для класса Stream документирует некоторые промежуточные операции, но не все. Возьмем, например, map:

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

Это промежуточная операция.

или filter

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

Это промежуточная операция.

Ни один из них не описывает, сохраняют ли они порядок.

qaru.site/info/505490/... утверждает:

Фактически каждая промежуточная операция сохраняет порядок по умолчанию. Единственными исключениями являются:

  • unordered(), который удаляет ограничение порядка.
  • sorted(), который изменяет порядок.

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

но есть ли официальная документация для этого?

Дополнительный кредит 😉

На самом деле есть две отдельные проблемы с порядком.

  • Выполняет ли вывод операции тот же порядок, что и вход?
  • Выполняется ли операция в каждом элементе в порядке.

Например, параллельная операция map может перемещаться по всем элементам в произвольном порядке (нарушая 2.), но все равно поддерживать порядок в возвращенном потоке (подчиняясь 1.)

Ответ 1

После некоторого исследования исходного кода я опустил следующие таблицы:

Взято из: Потоки в глубину - Глава 7: Разделитель

В следующей таблице показано, какие типы операций разрешены для изменения символов:

|                        | DISTICTS | SORTED | ORDERED | SIZED | SHORT_CIRCUIT |
| ---------------------- | -------- | ------ | ------- | ----- | --------------|
| source stream          | Y        | Y      | Y       | Y     | N             |
| intermediate operation | PCI      | PCI    | PCI     | PC    | PI            |
| terminal operation     | N        | N      | PC      | N     | PI            |
  • Y - разрешено иметь
  • P - май сохраняет
  • C - Может очищаться.
  • я - Может впрыскивать.
  • N - недействительно; Релевантно для операции.

Взято из Таблица характеристик потоков в глубину - поток

В следующей таблице показаны, какие характеристики и флаги будут выполняться каждый промежуточный оператор операции/терминала: (SHORT_CIRCUIT является релевантным только в контексте флагов StreamOpFlag)

Примечание. В каждую ячейку добавляется флаг P (Preserve), за исключением тех, которые имеют флаги C и I (Clear and Inject).

|                  |  DISTINCT |  SORTED |  ORDERED |  SIZED |  SHORT_CIRCUIT |
| ---------------- | ----------| --------| ---------| -------| ---------------|
|  filter          |           |         |          |  C     |                |
|  forEach         |           |         |  C       |        |                |
|  forEachOrdered  |           |         |          |        |                |
|  allMatch        |           |         |  C       |        |  I             |
|  distinct        |  I        |         |          |  C     |                |
|  flatMap         |  C        |  C      |          |  C     |                |
|  anyMatch        |           |         |  C       |        |  I             |
|  collect         |           |         |          |        |                |
|  unOrdered       |           |         |  C       |        |                |
|  count           |  C        |  C      |  C       |  C     |                |
|  findAny         |           |         |  C       |        |  I             |
|  findFirst       |           |         |          |        |  I             |
|  flatMapToXXX    |  C        |  C      |          |  C     |                |
|  limit           |           |         |          |  C     |  I             |
|  map             |  C        |  C      |          |        |                |
|  mapToXXX        |  C        |  C      |          |        |                |
|  max             |           |         |          |        |                |
|  min             |           |         |          |        |                |
|  noneMatch       |           |         |  C       |        |  I             |
|  peek            |           |         |          |        |                |
|  reduce          |           |         |          |        |                |
|  skip            |           |         |  C       |  I     |                |
|  sorted          |           |  I      |  I       |        |                |
|  toArray         |           |         |          |        |                |
  • C - Очищает.
  • I - Инъекции.

Ответ 2

Это звучит как два дубликата, так как оба ответа, которые вы связывали, действительно объясняют вещи. Я не могу сказать, должны ли map или filter сказать, что они зарезервировали порядок; они не полагаются на какое-либо предыдущее состояние или какое-либо другое государство (это операции без гражданства), поэтому подразумевается, что они сохраняют порядок, насколько я могу судить. Я вижу это наоборот, если они не заказывают заказ - это должно быть явно упомянуто в документах; если это не очевидно из названия операции. Например, Stream.generate не является очевидным для меня, если он генерирует упорядоченный поток; таким образом это сказано в документации по нему:

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

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

В целом есть два порядка: порядок обработки и порядок встреч.

Вы можете подумать о порядке встречи как обработке слева направо (представьте, что у вас есть List или array). Поэтому, если у вас есть конвейер, который не меняет порядок - элементы будут передаваться в Collector (или любую другую операцию терминала), как показано слева направо. Ну, не все терминальные операции похожи на это. Одно очевидное различие - forEach и forEachOrdered; или Collectors.toSet - который не требует предварительного сохранения начального порядка. Или возьмите findAny в качестве терминальной операции - очевидно, вам все равно, какой элемент вам нужен, так зачем вам кормить findAny в точном порядке в первую очередь?

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