Тема 23. Spliterator - BelyiZ/JavaCourses GitHub Wiki
- Интерфейс Сплитератора
- Методы Сплитератора
- Особенности использования Сплитератора
- Список литературы/курсов
Итераторы в Java
используются для прохождения элементов данного источника.
Spliterator
в Java
является одним из четырех доступных итераторов Java
— Iterator
, Enumeration
, ListIterator
и Spliterator
.
Как и Iterator
, Spliterator
используется для перебора элементов по одному из реализованного объекта списка, то есть для обхода элементов в источнике данных(Spliterator
поддерживает функции параллельного программирования.).
Это интерфейс, доступный в пакете java.util
.
Spliterator
был впервые представлен в Java 8 для поддержки параллельного программирования.
Его возможно использовать как для последовательной, так и для параллельной обработки элементов данных.
Основные функциональные возможности разделителя:
- Разделение исходных данных.
- Обработка исходных данных.
Преимущества Cплитератора:
- В отличие от Итератора и листератора, он поддерживает функции параллельного программирования.
- В отличие от Итератора и листератора, он поддерживает как последовательную, так и параллельную обработку данных.
- По сравнению с другими итераторами, он обеспечивает лучшую производительность. (Метод
tryAdvance ()
объединяет операцииnext ()
иhasNext ()
простого итератора). -
Spliterator
отлично работает как для источниковCollection
, так и для потоков, но не с реализациямиMap
в качестве источника.
Стрим из Сплитератора — это самый эффективный способ создания стрима.
Благодаря классу Spliterators
, можно преобразовать любой итератор в Сплитератор.
Таким образом Java Spliterator
необходимо использовать если необходимо:
- Иметь возможность параллельного программирования
- Иметь более высокую производительность (за счет метода
tryAdvance ()
)
Отличия Итератора от Сплитератора
Итератор | Cплитератор |
---|---|
Это итератор для API всей коллекции | Это итератор как для сбора, так и для потокового API, за исключением классов, реализованных на Map |
Это универсальный итератор | Это НЕ универсальный итератор |
Он НЕ поддерживает параллельное программирование | Он поддерживает параллельное программирование |
Java Spliterator
-это интерфейс в коллекции Java API
.
Сплитератор — это интерфейс, который содержит 8 методов, причём четыре из них уже имеют реализацию по умолчанию.
Оставшиеся методы — это tryAdvance
, trySplit
, estimateSize
и characteristics
.
Существуют также специальные модификации сплитератора для примитивных типов int, long и double: они добавляют несколько дополнительных методов.
Сплитератор похож на обычный итератор. Основное отличие — умение разделиться (split) на две части — лежит в основе параллельной работы потоков. В целях оптимизации сплитератор имеет ряд флагов-характеристик и может сообщить точно или приблизительно свой размер. Cплитератор никогда не модифицирует источник данных: у него нет метода remove как у итератора.
Сам Cплитератор (разделитель) не обеспечивает поведение параллельного программирования, но он предоставляет некоторые методы для его поддержки. Разработчики должны использовать методы интерфейса Spliterator и реализовывать параллельное программирование с помощью платформы Fork/Join.
Чтобы получить экземпляр Java Spliterator, будем использовать метод spliterator () :
import java.util.Spliterator;
import java.util.ArrayList;
import java.util.List;
public class SpliteratorSport
{
public static void main(String[] args)
{
List<String> FootballTeam = new ArrayList<>();
FootballTeam.add("Алексей Петров");
FootballTeam.add("Сергей Забивалов");
FootballTeam.add("Николай Башкоймяч");
}
}
Теперь нам нужно применить Spliterator
к Stream
. Конвертировать между ArrayList
и Stream
легко благодаря фреймворку Collections
:
Stream<String> FootballTeamStream = FootballTeam.stream();
//Создаем Stream из списка FootballTeam.
Spliterator<String> FootballTeamList = FootballTeamStream.spliterator();
// Получение объекта Spliterator в потоке футбольной команды.
-
characteristics()
: Возвращает характеристики, что Spliterator имеет как int значение. К ним относятся:-
SIZED
— способен возвращать точное количество элементов в источнике (точное количество элементов сплитератора), когда мы вызываем методvaluSize ()
. Такую характеристику возвращают сплитераторы всех коллекций. После некоторых потоковых операций (например,map()
илиsorted()
) она сохраняется, а после других (например,filter()
илиdistinct()
) — теряется. Она полезна для сортировки или, операцииtoArray()
: можно заранее выделить массив нужного размера. -
SUBSIZED
— когда мы разделяем экземпляр с помощьюtrySplit ()
и получаем такжеSIZED
SplitIterators. Эту характеристику возвращает сплитератор отArrayList
, потому что при делении он просто разбивает диапазон значений на два диапазона известной длины. А вотHashSet
её не вернёт, потому что он разбивает хэш-таблицу, для которой не известно, сколько содержится элементов в каждой половине. Соответственно дочерние сплитераторы уже не будут возвращать иSIZED
. -
ORDERED
— перебор упорядоченной последовательности. Если порядок данных имеет значение. К примеру, сплитератор отHashSet
не имеет этой характеристики, потому что порядок данных вHashSet
зависит от реализации. Отсутствие этой характеристики автоматически переведёт параллельный поток в неупорядоченный режим, благодаря чему он сможет работать быстрее. Раз в источнике данных порядка не было, то и дальше можно за ним не следить. -
SORTED
— перебирать отсортированную последовательность. В таком случае обязательно вернуть иORDERED
и переопределить методgetComparator()
, вернув компаратор сортировки илиnull
для "естественного порядка". Сортированные коллекции (например,TreeSet
) создают сплитератор с такой характеристикой, и с ней потоковая операцияsorted()
может быть пропущена. -
NONNULL
— источник гарантирует, что значения не равныNULL
(среди элементов нетnull
). Данную характеристику возвращает, например, сплитератор, созданныйConcurrentSkipListSet
: в эту структуру данныхnull
поместить нельзя. Также её возвращают все сплитераторы, созданные на примитивных типах. -
DISTINCT
— в нашей исходной последовательности нет дубликатов. Если элементы заведомо уникальны. ЛюбойSet
или поток после операцииdistinct()
создаёт сплитератор с такой характеристикой. Например, операцияdistinct()
на потоке изSet
выполняться не будет вообще и таким образом времени лишнего не займёт. -
IMMUTABLE
— если мы не можем структурно изменить источник элемента, то есть достоверно известно, что источник данных в процессе обхода заведомо не может измениться. Сплитераторы от обычных коллекций такую характеристику не возвращают, но её выдаёт, например, сплитератор отCollections.singletonList()
, потому что этот список изменить нельзя. -
CONCURRENT
— источник элемента может быть безопасно одновременно изменен. То сеть сплитератор остаётся рабочим после любых изменений источника. Такую характеристику сообщают сплитераторы коллекций изjava.util.concurrent
. Если сплитератор не имеет характеристикIMMUTABLE
иCONCURRENT
, то хорошо бы заставить его работать в fail-fast режиме, чтобы он возвращалConcurrentModificationException
, если заметит, что источник изменился.
-
-
forEachRemaining(E e)
: выполняет указанное действие для каждого оставшегося элемента в коллекции в последовательном порядке, пока все элементы не будут обработаны или действие не вызовет исключение.
Метод forEachRemaining (Consumer <? SuperT> action)
выполняет данное действие для каждого оставшегося элемента, последовательно в текущем потоке, пока все элементы не будут обработаны или действие не вызовет исключение:
FootballTeam.forEachRemaining(item -> System.out.println(item)); //по умолчанию неоднократно вызывает `tryAdvance ()`, пока не вернет false
3getComparator()
: если источник этого Spliterator
отсортирован Comparator
, он возвращает этот Comparator
.
Или же он возвращает ноль, если источник отсортирован в естественном порядке. Для источника, который не отсортирован, он выдаст исключение IllegalStateException
.
Comparator<string> comparator = FootballTeam.getComparator(); // выдаст IllegalStateException
4getExactSizeIfKnown()
: возвращает .estimateSize()
если размер известен, в противном случае возвращает "-1".
long size = FootballTeam.getExactSizeIfKnown(); // выдаст 3
5hasCharacteristics(int characteristics)
: возвращает true если .characteristics() Spliterator
содержит все указанные характеристики.
Необходим для проверки, имеет ли наш экземпляр данную характеристику или нет:
boolean isSized = FootballTeam.hasCharacteristics(Spliterator.SIZED); //true
boolean isSorted = FootballTeam.hasCharacteristics(Spliterator.SORTED); //false
boolean isNonNull = FootballTeam.hasCharacteristics(Spliterator.NONNULL); //false
6tryAdvance(E e)
: если оставшийся элемент существует, выполняет с ним заданное действие, возвращая true , иначе возвращает false .
Это по сути объединение методов итератора hasNext() и next().
Если у сплитератора есть следующий элемент, он должен вызвать переданную функцию с этим элементом и вернуть true, иначе функцию не вызывать и вернуть false.
boolean tryAdvance(Consumer<? super T> action)
Если Сплитератор еще не провел итерацию по всем элементам, он вернет значение true и выполнит действие со следующим элементом в источнике Сплитератора.
Затем переместит Сплитератор, чтобы указать на следующий элемент. Если нет элементов для повторения, вернет false
и не вызывет действие, потому что в любом случае нет элемента для осущесвления действия.
Если бы использовали итератор вместо Сплитератора, то должены были бы быть вызваны два метода вместо одного: hasNext
и next
.
Другими словами, он выполняет действие над следующим элементом в последовательности, а затем продвигает итератор.
while(FootballTeam.tryAdvance((item) -> System.out.println(item)));
Если у нас есть ORDERED Spliterator, действие выполняется над следующим элементом в порядке столкновения.
Примечания:
- Если
ORDERED
характеристика отсутствует, то нет никакой гарантии, какой элемент будет посещен Сплитератором, использующим этот метод, из оставшихся элементов. - После разделения каждый Сплитератор в исходной коллекции теперь будет выполнять итерацию по меньшему количеству элементов, чем при использовании одного итератора. Это означает, что этот метод проверяет, сколько элементов осталось у этого разделителя до завершения его подзадачи.
7. trySplit()
: если этот Spliterator может быть разделен, возвращает Spliterator покрывающие, которые после возврата из этого метода не будут охвачены этим Spliterator.
Это попытка поделиться надвое.
Метод возвращает новый сплитератор, который будет пробегать по первой половине исходного набора данных, при этом сам текущий сплитератор перепрыгивает на вторую половину.
Лучше всего, когда половины примерно равны, но это не обязательно.
Особенно неравномерно делятся сплитераторы с бесконечным набором данных: после деления один из сплитераторов обрабатывает конечный объём, а второй остаётся бесконечным.
Метод trySplit() имеет законное право не делиться и вернуть null (не случайно там try).
Обычно это делается, когда в текущем сплитераторе осталось мало данных (например только один элемент).
Spliterator<T> trySplit()
Другими словами:
Если разбиение возможно, метод trySplit ()
разделяет вызывающий Spliterator
и возвращает ссылку на элементы покрытия Spliterator, которые не будут покрываться этим Spliterator
по возвращении из этого метода. В противном случае возвращается ноль .
Таким образом, после успешного разделения оригинальный Spliterator будет выполнять итерацию по одной части последовательности, а возвращенный Spliterator
— по другой ее части.
Кроме того, возвращаемый Spliterator
охватывает строгий префикс элементов для начального ORDERED
Spliterator
(например, над списком ) :
// trySplit() method over ORDERED SpliteratorSport
Spliterator<String> SpliteratorSportNew = SpliteratorSport.trySplit();
// Elements in our SpliteratorSportNew = {"Алексей Петров", "Сергей Забивалов"}
if(SpliteratorSportNew != null) {
SpliteratorSportNew.forEachRemaining((n) -> System.out.println(n));
}
// Elements in our SpliteratorSport - {"Николай Башкоймяч"}
SpliteratorSport.forEachRemaining((n) -> System.out.println(n));
- Мы можем использовать
Spliterator
как для API коллекции, так и для классов Stream API. -
Spliterator
предоставляет характеристики объектов коллекции или API. -
Spliterator
невозможно использовать дляMap
реализованных классов. -
Spliterator
использует метод try Advance() для итерации элементов по отдельности в нескольких потоках для поддержки параллельной обработки. -
Spliterator
использует метод forEachRemaining() для последовательного перебора элементов в одном потоке. -
Spliterator
использует метод try Split (), чтобы разделить себя на разделители для поддержки параллельной обработки. - Разделитель поддерживает как последовательную, так и параллельную обработку данных.