Тема 23. Spliterator - BelyiZ/JavaCourses GitHub Wiki

Содержание:

  1. Интерфейс Сплитератора
  2. Методы Сплитератора
  3. Особенности использования Сплитератора
  4. Список литературы/курсов

Итераторы в Java используются для прохождения элементов данного источника. Spliterator в Java является одним из четырех доступных итераторов JavaIterator, Enumeration, ListIterator и Spliterator. Как и Iterator, Spliterator используется для перебора элементов по одному из реализованного объекта списка, то есть для обхода элементов в источнике данных(Spliterator поддерживает функции параллельного программирования.). Это интерфейс, доступный в пакете java.util. Spliterator был впервые представлен в Java 8 для поддержки параллельного программирования. Его возможно использовать как для последовательной, так и для параллельной обработки элементов данных.

Основные функциональные возможности разделителя:

  1. Разделение исходных данных.
  2. Обработка исходных данных.

Преимущества Cплитератора:

  1. В отличие от Итератора и листератора, он поддерживает функции параллельного программирования.
  2. В отличие от Итератора и листератора, он поддерживает как последовательную, так и параллельную обработку данных.
  3. По сравнению с другими итераторами, он обеспечивает лучшую производительность. (Метод tryAdvance () объединяет операции next () и hasNext () простого итератора).
  4. Spliterator отлично работает как для источников Collection, так и для потоков, но не с реализациями Map в качестве источника.

Стрим из Сплитератора — это самый эффективный способ создания стрима. Благодаря классу Spliterators, можно преобразовать любой итератор в Сплитератор.

Таким образом Java Spliterator необходимо использовать если необходимо:

  1. Иметь возможность параллельного программирования
  2. Иметь более высокую производительность (за счет метода 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 в потоке футбольной команды.

Методы Сплитератора

  1. characteristics() : Возвращает характеристики, что Spliterator имеет как int значение. К ним относятся:

    1. SIZED — способен возвращать точное количество элементов в источнике (точное количество элементов сплитератора), когда мы вызываем метод valuSize (). Такую характеристику возвращают сплитераторы всех коллекций. После некоторых потоковых операций (например, map() или sorted()) она сохраняется, а после других (например, filter() или distinct()) — теряется. Она полезна для сортировки или, операции toArray(): можно заранее выделить массив нужного размера.
    2. SUBSIZED — когда мы разделяем экземпляр с помощью trySplit () и получаем также SIZED SplitIterators. Эту характеристику возвращает сплитератор от ArrayList, потому что при делении он просто разбивает диапазон значений на два диапазона известной длины. А вот HashSet её не вернёт, потому что он разбивает хэш-таблицу, для которой не известно, сколько содержится элементов в каждой половине. Соответственно дочерние сплитераторы уже не будут возвращать и SIZED.
    3. ORDERED — перебор упорядоченной последовательности. Если порядок данных имеет значение. К примеру, сплитератор от HashSet не имеет этой характеристики, потому что порядок данных в HashSet зависит от реализации. Отсутствие этой характеристики автоматически переведёт параллельный поток в неупорядоченный режим, благодаря чему он сможет работать быстрее. Раз в источнике данных порядка не было, то и дальше можно за ним не следить.
    4. SORTED — перебирать отсортированную последовательность. В таком случае обязательно вернуть и ORDERED и переопределить метод getComparator(), вернув компаратор сортировки или null для "естественного порядка". Сортированные коллекции (например, TreeSet) создают сплитератор с такой характеристикой, и с ней потоковая операция sorted() может быть пропущена.
    5. NONNULL — источник гарантирует, что значения не равны NULL (среди элементов нет null). Данную характеристику возвращает, например, сплитератор, созданный ConcurrentSkipListSet: в эту структуру данных null поместить нельзя. Также её возвращают все сплитераторы, созданные на примитивных типах.
    6. DISTINCT — в нашей исходной последовательности нет дубликатов. Если элементы заведомо уникальны. Любой Set или поток после операции distinct() создаёт сплитератор с такой характеристикой. Например, операция distinct() на потоке из Set выполняться не будет вообще и таким образом времени лишнего не займёт.
    7. IMMUTABLE — если мы не можем структурно изменить источник элемента, то есть достоверно известно, что источник данных в процессе обхода заведомо не может измениться. Сплитераторы от обычных коллекций такую характеристику не возвращают, но её выдаёт, например, сплитератор от Collections.singletonList(), потому что этот список изменить нельзя.
    8. CONCURRENT — источник элемента может быть безопасно одновременно изменен. То сеть сплитератор остаётся рабочим после любых изменений источника. Такую характеристику сообщают сплитераторы коллекций из java.util.concurrent. Если сплитератор не имеет характеристик IMMUTABLE и CONCURRENT, то хорошо бы заставить его работать в fail-fast режиме, чтобы он возвращал ConcurrentModificationException, если заметит, что источник изменился.
  2. 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, действие выполняется над следующим элементом в порядке столкновения.

Примечания:

  1. Если ORDERED характеристика отсутствует, то нет никакой гарантии, какой элемент будет посещен Сплитератором, использующим этот метод, из оставшихся элементов.
  2. После разделения каждый Сплитератор в исходной коллекции теперь будет выполнять итерацию по меньшему количеству элементов, чем при использовании одного итератора. Это означает, что этот метод проверяет, сколько элементов осталось у этого разделителя до завершения его подзадачи.

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));

Особенности использования Сплитератора

  1. Мы можем использовать Spliterator как для API коллекции, так и для классов Stream API.
  2. Spliterator предоставляет характеристики объектов коллекции или API.
  3. Spliterator невозможно использовать для Map реализованных классов.
  4. Spliteratorиспользует метод try Advance() для итерации элементов по отдельности в нескольких потоках для поддержки параллельной обработки.
  5. Spliteratorиспользует метод forEachRemaining() для последовательного перебора элементов в одном потоке.
  6. Spliterator использует метод try Split (), чтобы разделить себя на разделители для поддержки параллельной обработки.
  7. Разделитель поддерживает как последовательную, так и параллельную обработку данных.

Список литературы/курсов

  1. https://docs.oracle.com/javase/8/docs/api/java/util/Spliterator.html
  2. https://www.baeldung.com/java-spliterator
  3. https://habr.com/ru/post/256905/

Тема 22. Stream | Оглавление | Тема 24. Исключения

⚠️ **GitHub.com Fallback** ⚠️