Java8 — быстрое начало работы с потоковым API

Java

Java 8 призвана помочь программистам писать более качественный код,
Его улучшения в основной библиотеке классов также являются ключевой частью.StreamЭто абстрактная концепция обработки коллекций Java8.
Он может указать операцию, которую вы хотите выполнить в коллекции, но время для выполнения операции оставлено на конкретную реализацию.

Зачем нужен Стрим?

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

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

Если бы вас попросили написать код в приведенном выше примере, вы могли бы написать следующее:

// 店铺属性
public class Property {
    String  name;
    // 距离,单位:米
    Integer distance;
    // 销量,月售
    Integer sales;
    // 价格,这里简单起见就写一个级别代表价格段
    Integer priceLevel;
    public Property(String name, int distance, int sales, int priceLevel) {
        this.name = name;
        this.distance = distance;
        this.sales = sales;
        this.priceLevel = priceLevel;
    }
    // getter setter 省略
}

Я хочу отфильтровать ближайшие ко мне магазины, вы можете написать такой код:

public static void main(String[] args) {
    Property p1 = new Property("叫了个鸡", 1000, 500, 2);
    Property p2 = new Property("张三丰饺子馆", 2300, 1500, 3);
    Property p3 = new Property("永和大王", 580, 3000, 1);
    Property p4 = new Property("肯德基", 6000, 200, 4);
    List<Property> properties = Arrays.asList(p1, p2, p3, p4);
    Collections.sort(properties, (x, y) -> x.distance.compareTo(y.distance));
    String name = properties.get(0).name;
    System.out.println("距离我最近的店铺是:" + name);
}

Здесь также используются частичные лямбда-выражения, которые вам, возможно, было труднее писать до Java 8.
Что делать, если вам приходится иметь дело с большим количеством элементов? Чтобы повысить производительность, вам необходимо распараллелить и использовать преимущества многоядерных архитектур.
Но писать параллельный код сложнее, чем использовать итераторы, а отладка достаточно хороша!

ноStreamКонечно, управлять этими вещами посередине очень просто, попробуйте:

// Stream操作
String name2 = properties.stream()
                .sorted(Comparator.comparingInt(x -> x.distance))
                .findFirst()
                .get().name;
System.out.println("距离我最近的店铺是:" + name);

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

Внешняя итерация и внутренняя итерация

Когда вы имеете дело с коллекцией, обычно ее перебирают, а затем обрабатывают каждый возвращаемый элемент. Например, я хочу увидеть количество магазинов с ежемесячными продажами больше 1000.

Итерация с циклом for

int count = 0;
for (Property property : properties) {
    if(property.sales > 1000){
        count++;
    }
}

Описанная выше операция будет работать, но вам потребуется много повторяющегося кода для каждой итерации. Также очень сложно модифицировать цикл for для параллельного выполнения.
Реализация каждого for должна быть изменена.

Исходя из принципа сбора, цикл for инкапсулирует синтаксический сахар итерации.Во-первых, вызывается метод итератора для создания объекта Iterator.
Затем контролируйте всю итерацию, котораявнешняя итерация. Итеративный процесс выполняется путем вызова методов hasNext и next объекта Iterator.

Расчет с помощью итераторов

int count = 0;
Iterator<Property> iterator = properties.iterator();
while(iterator.hasNext()){
    Property property = iterator.next();
    if(property.sales > 1000){
        count++;
    }
}

Итераторы также проблематичны. трудно абстрагироватьсяНеизвестный не может работать; Кроме того, это, по сути, сериализованная операция, и в общем случае
Цикл for путает поведение и метод.

Другой способ - сделать это с помощью внутренней итерации,properties.stream()Этот метод возвращает поток вместо итератора.

Вычислить с использованием внутренних итераций

long count = properties.stream()
                .filter(p -> p.sales > 1000)
                .count();

Приведенный выше код выполняется через Stream API, мы можем понять его как 2 шага:

  1. Найти все магазины с более чем 1000 продаж
  2. Рассчитать количество магазинов

Чтобы найти магазины с продажами более 1000, вам нужно сначала выполнить фильтр: filter, вы можете видеть, что входным параметром этого метода является функциональный интерфейс утверждения Predicate, упомянутый ранее.
После тестирования функции возвращаемое значение является логическим.
Из-за стиля Stream API мы не меняли содержимое коллекции, а описывали содержимое Stream и, наконец, вызывали метод count() для вычисления Stream.
Сколько в нем содержится отфильтрованных объектов, а возвращаемое значение длинное.

Создать поток

Вы уже знаете, что Java 8 добавляет в интерфейс Collection метод Stream, который может преобразовать любую коллекцию в Stream.
Если вы работаете с массивом, вы можете использоватьStream.of(1, 2, 3)метод преобразования его в поток.

Может быть, кто-то знает, что в JDK7 были добавлены некоторые библиотеки классов, такие какFiles.readAllLines(Paths.get("/home/biezhi/a.txt"))Такой способ чтения строк файла.
Listв видеCollectionУ подкласса есть методы для преобразования потока, поэтому мы читаем текстовый файл в строковую переменную, чтобы быть более кратким:

String content = Files.readAllLines(Paths.get("/home/biezhi/a.txt")).stream()
            .collect(Collectors.joining("\n"));

Сбор здесь будет объяснен позжеколлекционер, содержимое текстового файла получается после обработки Stream.

JDK8 также предоставляет нам несколько удобных библиотек классов, связанных с Stream:

Создать поток очень просто, попробуем проделать некоторые операции с созданным потоком.

потоковая операция

В java.util.stream.Stream определено много методов операций с потоками.Чтобы лучше понять Stream API, очень важно освоить его часто используемые операции.
Потоковые операции фактически можно разделить на две категории:Погрузочно-разгрузочные операции,Агрегатная операция.

  • Операции обработки: операции обработки, такие как фильтрация и сопоставление, извлекают поток слой за слоем и возвращают поток на следующий уровень для использования.
  • Операция агрегирования: генерировать результат из последнего потока для вызывающей стороны, foreach только обрабатывает и не возвращает.

filter

Фильтр также знает значение фильтрации, глядя на имя, Мы обычно используем его при фильтрации данных, и частота очень высока.
Параметры метода фильтра:Predicate<T> predicateто есть функция от T до булевой.


Отфильтровать магазины в радиусе 1000 метров от меня

properties.stream()
            .filter(p -> p.distance < 1000)

Отфильтровать магазины с именами, длина которых превышает 5 символов.

properties.stream()
            .filter(p -> p.name.length() > 5);

map

Иногда нам нужно преобразовать типы данных, обрабатываемые в потоке, В этом случае мы можем использовать метод карты для преобразования значений в потоке в новый поток.



Перечислите все названия магазинов

properties.stream()
            .map(p -> p.name);

Лямбда-выражение, переданное в карту, принимает параметр типа Property и возвращает строку.
Параметры и возвращаемое значение не обязательно должны быть одного типа, но лямбда-выражение должно быть экземпляром интерфейса Function.

flatMap

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


Например, у нас естьList<List<String>>Структурированные данные:

List<List<String>> lists = new ArrayList<>();
lists.add(Arrays.asList("apple", "click"));
lists.add(Arrays.asList("boss", "dig", "qq", "vivo"));
lists.add(Arrays.asList("c#", "biezhi"));

Операция, которую необходимо выполнить, состоит в том, чтобы получить количество слов, длина которых больше 2 в этих данных.

lists.stream()
        .flatMap(Collection::stream)
        .filter(str -> str.length() > 2)
        .count();

Возможно, вам придется выполнить 2 цикла for перед использованием flatMap. Здесь вызывается потоковый метод List для преобразования каждого списка в объект Stream.
В остальном все так же, как и раньше.

макс и мин

Одной из часто используемых операций в Stream является нахождение максимального и минимального значений, и операций max и min в Stream API достаточно для решения этой проблемы.

Нам нужно отфильтровать магазин с самой низкой ценой:

Property property = properties.stream()
            .max(Comparator.comparingInt(p -> p.priceLevel))
            .get();

Чтобы найти самый большой или самый маленький элемент в потоке, первое, что нужно рассмотреть, это то, что использовать в качестве индекса для сортировки.
В качестве примера возьмем поиск магазина с самой низкой ценой, индекс сортировки равенЦеновой класс магазина.

Чтобы отсортировать объект Stream по ценовому классу, передайте ему объект Comparator.
Java 8 предоставляет новый статический метод compareInt, который можно использовать для простой реализации компаратора.
Раньше нам нужно было сравнивать значение свойства двух объектов, но теперь нам нужно только предоставить метод доступа.

Соберите результаты

Обычно мы хотим увидеть результат после обработки потока, например, получение общего числа, преобразование результата, в предыдущем примере вы обнаружили, что вызов
Контекста после filter и map нет, и последующие операции должны выполняться вызовом метода collect в Stream.

Найдите 2 ближайших ко мне магазина

List<Property> properties = properties.stream()
            .sorted(Comparator.comparingInt(x -> x.distance))
            .limit(2)
            .collect(Collectors.toList());

Получить названия всех магазинов

List<String> names = properties.stream()
                      .map(p -> p.name)
                      .collect(Collectors.toList());

Получите уровень цен для каждого магазина

Map<String, Integer> map = properties.stream()
        .collect(Collectors.toMap(Property::getName, Property::getPriceLevel));

Список магазинов всех ценовых категорий

Map<Integer, List<Property>> priceMap = properties.stream()
                .collect(Collectors.groupingBy(Property::getPriceLevel));

Параллельная обработка данных

Параллелизм и параллелизм

Параллелизм — это когда две задачи разделяют период времени, а параллелизм — это когда две задачи выполняются одновременно, например, на многоядерном процессоре.
Если программа хочет запустить две задачи, и только один ЦП назначает им разные кванты времени, то это параллелизм, а не параллелизм.

Распараллеливание означает разделение задачи на несколько частей и их параллельное выполнение с целью сокращения времени выполнения задачи.

Это то же количество задач, что и при последовательном выполнении, разница как в использовании большего количества лошадей, чтобы тянуть телегу, затраты времени, естественно, сокращаются.
Фактически, при параллельном выполнении задач нагрузка на ЦП больше, чем при последовательном выполнении задач.

Распараллеливание данных относится к разделению данных на фрагменты и назначению отдельных процессоров для каждого фрагмента данных.

Возьмем снова пример с конной повозкой. Это все равно, что взять какой-то груз из повозки и поместить его на другую повозку, обе из которых следуют по одному и тому же пути к месту назначения.

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

Люди часто сравнивают параллелизм задач с параллелизмом данных, где поток другой и работа другая.
Контейнер приложения JavaEE, с которым мы чаще всего сталкиваемся, является одним из примеров распараллеливания задач.Каждый поток может не только обслуживать разных пользователей, но и
Вы также можете выполнять разные задачи для одного и того же пользователя, такие как вход в систему или добавление товаров в корзину.

Параллельный поток

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

  1. перечислитьStreamобъектparallelметод
  2. Вызывается при создании потокаparallelStreamвместо потокового метода

Поясним последовательные и параллельные потоки на конкретных примерах.

сериализованное вычисление

Отфильтровать 2 названия магазинов, уровень цен которых меньше 4, отсортированных по расстоянию

properties.stream()
            .filter(p -> p.priceLevel < 4)
            .sorted(Comparator.comparingInt(Property::getDistance))
            .map(Property::getName)
            .limit(2)
            .collect(Collectors.toList());

перечислитьparallelStreamметод может обрабатываться параллельно

properties.parallelStream()
            .filter(p -> p.priceLevel < 4)
            .sorted(Comparator.comparingInt(Property::getDistance))
            .map(Property::getName)
            .limit(2)
            .collect(Collectors.toList());

После прочтения этого вашей первой реакцией может быть немедленная замена метода потока в имеющемся коде на метод parallelStream.
Потому что это так легко сделать! Не будьте слишком заняты, чтобы получить максимальную отдачу от вашего оборудования, важно использовать преимущества параллелизма, но параллелизм данных, обеспечиваемый потоковыми библиотеками, — это только одна из форм этого.

Давайте сначала зададим себе вопрос:Быстрее ли запускать потоковый код параллельно, чем последовательно?Это не простой вопрос.
Возвращаясь к предыдущему примеру, какой метод занимает больше времени, зависит от контекста сериализованной или параллельной среды выполнения.