В предыдущей статье я объяснил основные принципы Stream и его основные методы, в этой статье мы продолжим объяснять другие операции потока.
Если вы не читали предыдущую статью, вы можете нажать, чтобы сначала изучить ее.Простая и быстрая обработка коллекций — Java8 Stream (on)
Стоит отметить, что вы должны узнать о lambda, прежде чем изучать Stream. В этой статье также предполагается, что читатель уже имеет некоторое представление о лямбда-выражениях.
Основное содержание этой статьи:
- Специализированная форма потока — числовой поток
- Необязательный класс
- Как построить поток
- метод сбора
- Проблемы, связанные с параллельным потоком
1. Числовой поток
как описано ранееint sum = list.stream().map(Person::getAge).reduce(0, Integer::sum);
Метод расчета суммы элементов имеет неявные затраты на упаковку,map(Person::getAge)
После метода поток становится типом Stream, и каждый Integer должен быть распакован в примитивный тип, а затем суммирован методом sum, что сильно влияет на эффективность.
В ответ на эту проблему в Java 8 добросовестно введен числовой поток IntStream, DoubleStream, LongStream, элементы в этом потоке — все примитивные типы данных, а именно int, double, long
1. Преобразование потоков и числовых потоков
Преобразование потока в числовой поток
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
IntStream intStream = list.stream().mapToInt(Person::getAge);
Конечно, если это следующее, это пойдет не так
LongStream longStream = list.stream().mapToInt(Person::getAge);
Поскольку метод getAge возвращает тип int (если он возвращает Integer, его также можно преобразовать в IntStream).
Преобразование потока значений в поток
Очень просто, просто в коробке
Stream<Integer> stream = intStream.boxed();
2. Метод численного потока
Излишне говорить, что следующие методы работают, просто посмотрите на названия:
- sum()
- max()
- min()
- средний () и т. д.
3. Диапазон значений
IntStream и LongStream имеют методы range и rangeClosed для обработки числового диапазона.
- IntStream : rangeClosed (int, int) / диапазон (int, int)
- LongStream : rangeClosed(long, long) / range(long, long)
Разница между этими двумя методами заключается в том, что один из них представляет собой закрытый интервал, а другой — полуоткрытый полузакрытый интервал:
- диапазонЗакрыто(1, 100) : [1, 100]
- диапазон(1, 100) : [1, 100)
мы можем использоватьIntStream.rangeClosed(1, 100)
Сгенерировать поток значений от 1 до 100
求 1 到 10 的数值总和:
IntStream intStream = IntStream.rangeClosed(1, 10);
int sum = intStream.sum();
2. Необязательный класс
Можно сказать, что NullPointerException — это слово, которое ненавидит каждый Java-программист.Для этой проблемы в Java 8 представлен новый контейнерный классOptional, который может представлять существование или отсутствие значения, так что нет необходимости возвращать проблемный ноль. Этот класс часто встречается в коде из предыдущей статьи, и он также является улучшением этой проблемы.
Вот несколько часто используемых методов класса Optional:
- isPresent(): возвращает true, если значение существует, иначе false.
- get() : возвращает текущее значение, выдает исключение, если значение не существует
- orElse(T): возвращает значение, если значение существует, в противном случае возвращает значение T
Класс Optional также имеет три специализированные версии:OptionalInt,OptionalLongиOptionalDouble.Тип, возвращаемый методом max в только что упомянутом потоке значений, таков:
В факультативном классе на самом деле очень много знаний. Возможно, потребуется открыть статью, чтобы объяснить это. Сначала я расскажу о многом здесь, а затем вы сможете в основном узнать, как его использовать.
3. Создайте поток
До того, как мы получили поток, который был преобразован из исходного источника данных, по сути, мы также можем напрямую сконструировать поток.
1. Поток создания ценности
- Stream.of(T...) : Stream.of("aa", "bb") создает поток
生成一个字符串流
Stream<String> stream = Stream.of("aaa", "bbb", "ccc");
- Stream.empty(): создать пустой поток
2. Поток создания массива
Создайте соответствующий поток на основе типа массива параметра:
- Arrays.stream(T[ ])
- Arrays.stream(int[ ])
- Arrays.stream(double[ ])
- Arrays.stream(long[ ])
Стоит отметить, что вы также можете указать, что берется только определенная часть массива, используяArrays.stream(T[], int, int)
只取索引第 1 到第 2 位的:
int[] a = {1, 2, 3, 4};
Arrays.stream(a, 1, 3).forEach(System.out :: println);
打印 2 ,3
3. Процесс создания файла
Stream<String> stream = Files.lines(Paths.get("data.txt"));
Каждый элемент является одной из строк данного файла
4. Функции генерируют потоки
Два метода:
- iterate : по очереди применяет функцию к каждому вновь сгенерированному значению
- generate : принимает функцию, которая генерирует новое значение
Stream.iterate(0, n -> n + 2)
生成流,首元素为 0,之后依次加 2
Stream.generate(Math :: random)
生成流,为 0 到 1 的随机双精度数
Stream.generate(() -> 1)
生成流,元素全为 1
4. собирать данные
В качестве терминальной операции метод coollect принимает параметр интерфейса Collector, который может выполнять некоторые операции по сбору и суммированию данных.
1. Коллекция
Самый распространенный метод, собирающий все элементы в потоке в список, набор или коллекцию.
- toList
- toSet
- toCollection
- toMap
List newlist = list.stream.collect(toList());
//如果 Map 的 Key 重复了,可是会报错的哦
Map<Integer, Person> map = list.stream().collect(toMap(Person::getAge, p -> p));
2. Резюме
(1) подсчет
Для расчета суммы:
long l = list.stream().collect(counting());
Да, вы должны были подумать об этом, также возможно следующее:
long l = list.stream().count();
Второй рекомендованный
(2) суммированиеInt, суммированиеLong, суммированиеDouble
Суммирование, да, тоже вычисляет сумму, но здесь требуется параметр функции
Рассчитайте сумму возрастов людей:
int sum = list.stream().collect(summingInt(Person::getAge));
Конечно, это также можно упростить до:
int sum = list.stream().mapToInt(Person::getAge).sum();
В дополнение к двум вышеперечисленным вы можете:
int sum = list.stream().map(Person::getAge).reduce(Interger::sum).get();
Второй рекомендованный
Таким образом, функциональное программирование обычно предоставляет несколько способов выполнения одной и той же операции.
(3) averagingInt, averagingLong, averagingDouble
Просто посмотрите на имя, найдите среднее
Double average = list.stream().collect(averagingInt(Person::getAge));
Конечно, вы также можете написать
OptionalDouble average = list.stream().mapToInt(Person::getAge).average();
Однако следует отметить, что два возвращаемых значения имеют разные типы.
(4) summarizingInt, summarizingLong, summarizingDouble
Эти три метода являются специальными, например, summarizingInt вернет тип IntSummaryStatistics.
IntSummaryStatistics l = list.stream().collect(summarizingInt(Person::getAge));
IntSummaryStatistics содержит рассчитанное среднее, общее, сумму и большинство значений, а соответствующие данные можно получить следующими методами.
3. Берите наибольшую ценность
maxBy, minBy два метода, нужен интерфейс Comparator в качестве параметра
Optional<Person> optional = list.stream().collect(maxBy(comparing(Person::getAge)));
Мы также можем напрямую использовать метод max, чтобы получить тот же результат.
Optional<Person> optional = list.stream().max(comparing(Person::getAge));
4. Присоединение к строке подключения
Это также более распространенный способ соединения строковых элементов в потоке.Его базовая реализация использует StringBuilder, который специально используется для конкатенации строк.
String s = list.stream().map(Person::getName).collect(joining());
结果:jackmiketom
String s = list.stream().map(Person::getName).collect(joining(","));
结果:jack,mike,tom
Также существует специальный перегруженный метод для присоединения:
String s = list.stream().map(Person::getName).collect(joining(" and ", "Today ", " play games."));
结果:Today jack and mike and tom play games.
То есть Сегодня ставь начало, играй в игры, ставь конец, и соединяй каждую ниточку посередине
5. группировкаПо
groupingBy используется для группировки данных и, наконец, возвращает тип карты.
Map<Integer, List<Person>> map = list.stream().collect(groupingBy(Person::getAge));
В примере мы сгруппированы по возрасту, и каждый объект Person с одинаковым возрастом сгруппирован вместе.
Кроме того, видно, чтоPerson::getAge
Определяет ключ карты (целочисленный тип), а тип списка определяет значение карты (тип списка)
Многоуровневая группировка
groupingBy может принимать второй параметр для реализации многоуровневой группировки:
Map<Integer, Map<T, List<Person>>> map = list.stream().collect(groupingBy(Person::getAge, groupingBy(...)));
Возвращенный ключ Map имеет тип Integer, а значение имеет тип Map
Собирать данные по группам
Map<Integer, Integer> map = list.stream().collect(groupingBy(Person::getAge, summingInt(Person::getAge)));
В этом примере мы группируем по возрасту, а затемsummingInt(Person::getAge))
Вычислите сумму возрастов (целое число) каждой группы отдельно и, наконец, верните Map
По этому методу мы можем узнать, что писали ранее:
groupingBy(Person::getAge)
на самом деле эквивалентно:
groupingBy(Person::getAge, toList())
6. разбиение по разделам
Разница между партиционированием и группировкой в том, что партиции делятся в соответствии с истинным и ложным, поэтому лямбда параметров, принимаемых partitioningBy, такжеT -> boolean
根据年龄是否小于等于20来分区
Map<Boolean, List<Person>> map = list.stream()
.collect(partitioningBy(p -> p.getAge() <= 20));
打印输出
{
false=[Person{name='mike', age=25}, Person{name='tom', age=30}],
true=[Person{name='jack', age=20}]
}
Аналогично, partitioningBy также может добавить сборщик в качестве второго параметра для выполнения множественного разделения и других операций, подобных groupBy.
5. Параллельно
мы проходимlist.stream()
Чтобы преобразовать тип списка в тип потока, мы также можем передатьlist.parallelStream()
Преобразование в параллельный поток. Таким образом, вы обычно можете использовать parallelStream вместо метода потока.
Параллельная потоковая передача предназначена для разделения содержимого на несколько блоков данных и использования разных потоков для обработки потока каждого блока данных отдельно. Это тоже важная особенность потоков.Вы должны знать, что до Java 7 было очень хлопотно обрабатывать коллекции данных параллельно, приходилось самостоятельно делить данные, самостоятельно распределять потоки и при необходимости обеспечивать синхронизацию, чтобы избежать конкуренции.
Stream позволяет программистам легко реализовать параллельную обработку наборов данных, но следует отметить, что он подходит не для всех ситуаций: иногда параллельная обработка даже менее эффективна, чем последовательная, а иногда из-за проблем с безопасностью потоков может также привести к Ошибки обработки данных, о которых я расскажу в следующей статье.
Например, следующий пример
int i = Stream.iterate(1, a -> a + 1).limit(100).parallel().reduce(0, Integer::sum);
Мы вычисляем сумму всех чисел от 1 до 100 с помощью такой строки кода и используем parallel для достижения параллелизма.
Но на самом деле такие вычисления очень неэффективны, даже ниже, чем без использования параллелизма! С одной стороны, это из-за проблемы с боксом, о которой тоже упоминалось ранее, поэтому повторяться не буду, с другой стороны, итеративному методу сложно разбить эти числа на несколько независимых блоков для параллельного выполнения , так что эффективность незаметно снижается.
Разложимость потоков
Это касается вопроса разложимости потоков.При использовании параллелизма мы должны обращать внимание на то, легко ли разложить структуру данных, стоящую за потоком. Например, хорошо известные ArrayList и LinkedList, первый явно лучше в декомпозиции.
Давайте посмотрим на разложимость некоторых источников данных.
источник данных | разложимость |
---|---|
ArrayList | отлично |
LinkedList | Разница |
IntStream.range | отлично |
Stream.iterate | Разница |
HashSet | хорошо |
TreeSet | хорошо |
последовательный
В дополнение к разложимости и только что упомянутой проблеме упаковки, также стоит отметить, что некоторые операции сами по себе выполняются хуже на параллельных потоках, чем на последовательных, например: limit, findFirst, потому что эти два метода будут учитывать порядок элементов, а Сам по себе parallel противоречит порядку, и из-за этого findAny обычно более эффективен, чем findFirst.
6. Эффективность
Наконец, поговорим об эффективности, о неэффективности Stream, возможно, слышали многие. Фактически, для некоторых простых операций, таких как простой обход, поиск наибольшего значения и т. д., производительность Stream действительно ниже, чем у традиционных реализаций циклов или итераторов, или даже намного ниже.
Но для сложных операций, таких как сокращение некоторых сложных объектов, производительность Stream сравнима с ручным выполнением, и в некоторых случаях использование параллельных потоков может быть намного эффективнее, чем ручное внедрение. На лезвии используется хорошая сталь, и она может быть использована с максимальной пользой, когда используется в подходящей сцене.
Появление функциональных интерфейсов в основном связано с повышением эффективности разработки кода и повышением читабельности кода, в то же время в реальной разработке не всегда требуется очень высокая производительность, поэтому появление Stream и lambda по-прежнему имеет большое значение.
Связанное Чтение
Вам также может понравиться
- Вы должны выяснить String, StringBuilder, StringBuffer
- Поделитесь некоторыми личными галантереями бэкэнда Java
- Научу вас Shiro + SpringBoot интегрировать JWT
- Научу вас Широ интегрировать SpringBoot и избегать всевозможных ям