Skip to content

Класс Stream и parallelStream()

Введение

С введением Java 8 был представлен API потоков (Stream API), который позволяет работать с последовательностями данных более декларативно и эффективно. Класс Stream является центральным элементом этого API и предоставляет мощные инструменты для обработки коллекций и массивов данных.

Определение Stream

Stream представляет собой последовательность элементов, которые могут быть обработаны с использованием различных операций, таких как фильтрация, преобразование, сортировка и агрегация. Потоки могут быть созданы из различных источников, таких как коллекции, массивы, I/O каналы и генераторы.

Основные характеристики Stream

  1. Неизменяемость: Потоки являются неизменяемыми. Операции над потоками не изменяют исходные данные, а создают новые потоки.

  2. Ленивые вычисления: Большинство операций над потоками являются ленивыми, что означает, что они не выполняются до тех пор, пока не будет вызван терминальный метод (например, forEach, collect, reduce).

  3. Параллельные вычисления: Потоки могут быть обработаны параллельно, что позволяет эффективно использовать многопоточность для обработки больших объемов данных.

  4. Поддержка различных типов данных: Stream может работать с объектами, примитивами (через специализированные потоки, такие как IntStream, DoubleStream, LongStream) и даже с потоками данных из I/O.

Создание Stream

Существует несколько способов создания потоков:

  1. Из коллекций: Используя метод stream() для коллекций, таких как List, Set, и т.д. java List<String> list = Arrays.asList("a", "b", "c"); Stream<String> streamFromList = list.stream();

  2. Из массивов: Используя метод Arrays.stream(). java String[] array = {"a", "b", "c"}; Stream<String> streamFromArray = Arrays.stream(array);

  3. Из значений: Используя статические методы Stream.of(). java Stream<String> streamOfValues = Stream.of("a", "b", "c");

  4. Из файлов: Используя Files.lines() для чтения строк из файла. java Stream<String> streamFromFile = Files.lines(Paths.get("file.txt"));

  5. Генерация потоков: Используя методы Stream.iterate() и Stream.generate(). ```java Stream infiniteStream = Stream.iterate(0, n -> n + 1);

    Random random = new Random(); // Генерируем бесконечный поток случайных целых чисел Stream randomStream = Stream.generate(random::nextInt); // Ограничиваем поток первыми 10 значениями и выводим их randomStream.limit(10).forEach(System.out::println);

```

Операции над Stream

Потоки поддерживают два типа операций: промежуточные и терминальные.

  1. Промежуточные операции: Эти операции возвращают новый поток и могут быть объединены. Примеры:
  2. filter(Predicate<? super T> predicate): фильтрует элементы по заданному условию.
  3. map(Function<? super T, ? extends R> mapper): преобразует элементы.
  4. sorted(): сортирует элементы.

  5. Терминальные операции: Эти операции возвращают результат и закрывают поток. Примеры:

  6. forEach(Consumer<? super T> action): выполняет действие для каждого элемента.
  7. collect(Collector<? super T, A, R> collector): собирает элементы в коллекцию.
  8. reduce(B identity, BiFunction<? super B, ? super T, ? extends B> accumulator): выполняет агрегацию.

Примеры использования Stream

Пример 1: Простой пример с фильтрацией и выводом
import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Фильтруем имена, начинающиеся с буквы "A" и выводим их
        names.stream()
             .filter(name -> name.startsWith("A"))
             .forEach(System.out::println); // Вывод: Alice
    }
}
Пример 2: Преобразование и сбор данных
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class CollectExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");

        // Преобразуем имена в верхний регистр и собираем в новый список
        List<String> upperCaseNames = names.stream()
                                            .map(String::toUpperCase) // Преобразуем в верхний регистр
                                            .collect(Collectors.toList()); // Собираем в новый список

        System.out.println(upperCaseNames); // Вывод: [ALICE, BOB, CHARLIE, DAVID]
    }
}
Пример 3: Сортировка и уникальные элементы
import java.util.Arrays;
import java.util.List;

public class UniqueSortedExample {
    public static void main(String[] args) {
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David", "Alice");

        // Получаем уникальные имена, сортируем их и выводим
        names.stream()
             .distinct() // Убираем дубликаты
             .sorted() // Сортируем
             .forEach(System.out::println); // Вывод: Alice, Bob, Charlie, David
    }
}
Пример 4: Использование reduce для агрегации
import java.util.Arrays;
import java.util.List;

public class ReduceExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        // Используем reduce для суммирования чисел
        int sum = numbers.stream()
                         .reduce(0, Integer::sum); // Начальное значение 0

        System.out.println("Сумма: " + sum); // Вывод: Сумма: 15
    }
}
Пример 5: Параллельная обработка с Stream
import java.util.Arrays;
import java.util.List;

public class ParallelStreamExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        // Используем параллельный поток для суммирования чисел
        int sum = numbers.parallelStream()
                         .reduce(0, Integer::sum); // Начальное значение 0

        System.out.println("Сумма (параллельно): " + sum); // Вывод: Сумма (параллельно): 55
    }
}

Заключение

Класс Stream в Java предоставляет мощные инструменты для работы с последовательностями данных. Он позволяет писать более декларативный и выразительный код, упрощая операции над коллекциями и массивами.

Потоки поддерживают множество операций, включая фильтрацию, преобразование, сортировку и агрегацию, а также позволяют использовать параллельные вычисления для повышения производительности. Понимание и использование Stream API может значительно улучшить качество вашего кода и упростить работу с данными в Java.

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

Как работает parallelStream()

  1. Разделение работы:
  2. Когда вы вызываете parallelStream(), Java разбивает коллекцию на несколько частей, которые могут обрабатываться одновременно в разных потоках. Это позволяет использовать многоядерные процессоры для повышения производительности.

  3. Обработка в потоках:

  4. Каждый поток выполняет свою часть работы. В вашем примере каждый поток будет суммировать часть чисел из списка. Например, если у вас 10 чисел, Java может разделить их на 2 или 5 частей, в зависимости от доступных ресурсов и реализации.

  5. Сбор результатов:

  6. После того как все потоки завершат свою работу, результаты их вычислений (в данном случае частичные суммы) будут объединены. В случае использования метода reduce, Java автоматически объединит результаты, используя указанную функцию (в данном случае Integer::sum).

Когда вы получите конечное значение?

Вы получите конечное значение только после того, как все потоки завершат свою работу. Это происходит следующим образом:

  • Когда вы вызываете reduce, Java начинает обрабатывать данные в параллельных потоках.
  • Каждый поток выполняет свою часть работы и возвращает частичный результат.
  • После завершения всех потоков Java объединяет эти частичные результаты в одно итоговое значение.
  • Ваша переменная sum будет содержать это итоговое значение только после завершения всех потоков.

Пример работы

В вашем примере:

int sum = numbers.parallelStream()
                 .reduce(0, Integer::sum);
  • numbers.parallelStream() создает параллельный поток из списка чисел.
  • reduce(0, Integer::sum) начинает процесс суммирования, используя 0 как начальное значение.
  • Java разбивает список на части, и каждый поток суммирует свою часть.
  • После завершения всех потоков, результаты суммируются, и итоговое значение присваивается переменной sum.

Заключение

Таким образом, при использовании параллельных потоков вы получите конечное значение только после того, как все потоки завершат свою работу. Это позволяет эффективно использовать ресурсы процессора и ускорять выполнение задач, особенно при работе с большими объемами данных.