Тема 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 ()и получаем такжеSIZEDSplitIterators. Эту характеристику возвращает сплитератор от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 ()`, пока не вернет false3getComparator() : если источник этого Spliterator отсортирован Comparator , он возвращает этот Comparator .
Или же он возвращает ноль, если источник отсортирован в естественном порядке. Для источника, который не отсортирован, он выдаст исключение IllegalStateException .
Comparator<string> comparator = FootballTeam.getComparator(); // выдаст IllegalStateException4getExactSizeIfKnown() : возвращает .estimateSize() если размер известен, в противном случае возвращает "-1".
long size = FootballTeam.getExactSizeIfKnown(); // выдаст 35hasCharacteristics(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); //false6tryAdvance(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 (), чтобы разделить себя на разделители для поддержки параллельной обработки. - Разделитель поддерживает как последовательную, так и параллельную обработку данных.