Неразделимые сплитераторы

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

Вопросы:

  1. Есть ли разница между обычным итератором и Spliterator что возвращает ноль безоговорочно? Кажется, что такой сплитератор побеждает, ну, в общем, расщепление.
  2. Конечно, есть законные случаи использования сплитераторов, которые условно возвращают ноль на trySplit(), но есть ли законный вариант использования сплитератора, который безоговорочно возвращает ноль?

3 ответа

Хотя основным преимуществом Spliterator перед Iterator, как вы сказали, является его метод trySplit(), который позволяет его распараллеливать, существуют и другие существенные преимущества:

http://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html

Spliterator API был разработан для поддержки эффективного параллельного обхода в дополнение к последовательному обходу, поддерживая декомпозицию и одноэлементную итерацию. Кроме того, протокол для доступа к элементам через Spliterator предназначен для наложения меньших накладных расходов на элемент, чем Iterator, и во избежание внутренней гонки, связанной с наличием отдельных методов для hasNext() и next().

Кроме того, Spliterators можно напрямую преобразовать в потоки с помощью StreamSupport.stream, чтобы использовать потоки Java8.

Одна из целей Spliterator это возможность разделить, но это не единственная цель. Другой основной целью является поддержка класса для создания собственного Stream источник. Один из способов создания потокового источника - реализовать собственный Spliterator и передать его StreamSupport.stream, Самое простое, что можно сделать, - это написать Spliterator, который не может быть разделен. Это заставляет поток выполняться последовательно, но это может быть приемлемо для всего, что вы пытаетесь сделать.

Есть и другие случаи, когда написание неразделимого Spliterator имеет смысл. Например, в OpenJDK есть реализации, такие как EmptySpliterator которые не содержат элементов. Конечно, это нельзя разделить. Аналогичный случай - одноэлементный сплитератор, который содержит ровно один элемент. Это тоже нельзя разделить. Обе реализации возвращают null безусловно из trySplit,

Другой случай, когда написание неразделимого Spliterator является простым и эффективным, а объем кода, необходимого для реализации разделяемого, является непомерно высоким. (По крайней мере, не стоит того, чтобы записывать его в ответ переполнения стека.) Например, см. Пример Spliterator из этого ответа. Дело в том, что реализация Spliterator хочет обернуть другой Spliterator и сделать что-то особенное, в этом случае проверьте, не является ли он пустым. В противном случае он просто передает все обернутый Spliterator. Делать это с неразделимым Spliterator довольно легко.

Обратите внимание, что в этом ответе есть обсуждение, комментарий к этому ответу в моем ответе на тот же вопрос и цепочка комментариев к моему ответу о том, как можно создать сплитератор с разделением (то есть готовый параллельно). Но на самом деле никто не написал код для разделения.:-) В зависимости от того, сколько лени вы хотите сохранить в исходном потоке, и сколько параллельной эффективности вы хотите, написание разделяемого Spliterator может быть довольно сложным.

По моим оценкам, сделать это несколько проще, написав Iterator вместо Spliterator (как в моем ответе, указанном выше). Оказывается, что Spliterators.spliteratorUnknownSize может обеспечить ограниченное количество параллелизма, даже от Итератора, который, очевидно, является чисто последовательной конструкцией. Это происходит в течение IteratorSpliterator, который извлекает несколько элементов из Итератора и обрабатывает их в пакетном режиме. К сожалению, размер пакета жестко задан, но, по крайней мере, это дает возможность обрабатывать элементы, извлекаемые из Итератора параллельно в некоторых случаях.

Есть больше преимуществ, чем просто разделение поддержки:

  • Логика итерации содержится в одном tryAdvance метод, а не распространяется на два метода, такие как hasNext, next, Разделение логики на два метода усложняет много Iterator реализации, так как это часто подразумевает, что hasNext Метод должен выполнить фактическую попытку запроса, которая может привести к значению, которое затем необходимо запомнить для последующего next вызов. И тот факт, что этот запрос был сделан, также должен быть запомнен, явно или неявно.

    Было бы проще, если бы была гарантия, что hasNext/next всегда называются типичным чередующимся способом, однако такой гарантии нет.

    Одним из примеров является BufferedReader.readLine() который имеет простой tryAdvance логика. Упаковка Iterator должен вызвать этот метод в пределах hasNext реализация и запомнить линию для next вызов. (Как ни странно, нынешний BufferedReader.stream() реализация действительно реализует такой сложный Iterator это будет завернуто в Spliterator вместо реализации гораздо проще Spliterator непосредственно. Кажется, что проблему "я не знаком с этим" не следует недооценивать)

  • estimateSize(); Spliterator может вернуть оценку (или даже точное число) оставшихся элементов, которые можно использовать для предварительного распределения ресурсов. Это может повысить эффективность.

  • characteristics(); Spliterators может предоставить дополнительную информацию об их содержании или поведении. Помимо того, чтобы сказать, является ли предполагаемый размер точным размером, вы можете узнать, можете ли вы увидеть null значения, есть ли определенный порядок встречи или все значения различны. Конкретный алгоритм может воспользоваться этим. Очевидно, что Stream API представляет собой набор таких алгоритмов, которые могут использовать преимущества, поэтому при планировании создания (или поддержки создания) потоков и возможности выбора при реализации Spliterator предпочтительнее сообщать как можно больше метаинформации Iterator это будет завернуто позже.

Другие вопросы по тегам