Класс Stream и parallelStream()
Введение
С введением Java 8 был представлен API потоков (Stream API), который позволяет работать с последовательностями данных более декларативно и эффективно. Класс Stream
является центральным элементом этого API и предоставляет мощные инструменты для обработки коллекций и массивов данных.
Определение Stream
Stream
представляет собой последовательность элементов, которые могут быть обработаны с использованием различных операций, таких как фильтрация, преобразование, сортировка и агрегация. Потоки могут быть созданы из различных источников, таких как коллекции, массивы, I/O каналы и генераторы.
Основные характеристики Stream
-
Неизменяемость: Потоки являются неизменяемыми. Операции над потоками не изменяют исходные данные, а создают новые потоки.
-
Ленивые вычисления: Большинство операций над потоками являются ленивыми, что означает, что они не выполняются до тех пор, пока не будет вызван терминальный метод (например,
forEach
,collect
,reduce
). -
Параллельные вычисления: Потоки могут быть обработаны параллельно, что позволяет эффективно использовать многопоточность для обработки больших объемов данных.
-
Поддержка различных типов данных:
Stream
может работать с объектами, примитивами (через специализированные потоки, такие какIntStream
,DoubleStream
,LongStream
) и даже с потоками данных из I/O.
Создание Stream
Существует несколько способов создания потоков:
-
Из коллекций: Используя метод
stream()
для коллекций, таких какList
,Set
, и т.д.java List<String> list = Arrays.asList("a", "b", "c"); Stream<String> streamFromList = list.stream();
-
Из массивов: Используя метод
Arrays.stream()
.java String[] array = {"a", "b", "c"}; Stream<String> streamFromArray = Arrays.stream(array);
-
Из значений: Используя статические методы
Stream.of()
.java Stream<String> streamOfValues = Stream.of("a", "b", "c");
-
Из файлов: Используя
Files.lines()
для чтения строк из файла.java Stream<String> streamFromFile = Files.lines(Paths.get("file.txt"));
-
Генерация потоков: Используя методы
Stream.iterate()
иStream.generate()
. ```java StreaminfiniteStream = 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
Потоки поддерживают два типа операций: промежуточные и терминальные.
- Промежуточные операции: Эти операции возвращают новый поток и могут быть объединены. Примеры:
filter(Predicate<? super T> predicate)
: фильтрует элементы по заданному условию.map(Function<? super T, ? extends R> mapper)
: преобразует элементы.-
sorted()
: сортирует элементы. -
Терминальные операции: Эти операции возвращают результат и закрывают поток. Примеры:
forEach(Consumer<? super T> action)
: выполняет действие для каждого элемента.collect(Collector<? super T, A, R> collector)
: собирает элементы в коллекцию.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()
- Разделение работы:
-
Когда вы вызываете
parallelStream()
, Java разбивает коллекцию на несколько частей, которые могут обрабатываться одновременно в разных потоках. Это позволяет использовать многоядерные процессоры для повышения производительности. -
Обработка в потоках:
-
Каждый поток выполняет свою часть работы. В вашем примере каждый поток будет суммировать часть чисел из списка. Например, если у вас 10 чисел, Java может разделить их на 2 или 5 частей, в зависимости от доступных ресурсов и реализации.
-
Сбор результатов:
- После того как все потоки завершат свою работу, результаты их вычислений (в данном случае частичные суммы) будут объединены. В случае использования метода
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
.
Заключение
Таким образом, при использовании параллельных потоков вы получите конечное значение только после того, как все потоки завершат свою работу. Это позволяет эффективно использовать ресурсы процессора и ускорять выполнение задач, особенно при работе с большими объемами данных.