Java 8 Stream: разница между limit() и skip()



про Streams, когда я выполняю этот кусок кода

public class Main {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6,7,8,9)
        .peek(x->System.out.print("nA"+x))
        .limit(3)
        .peek(x->System.out.print("B"+x))
        .forEach(x->System.out.print("C"+x));
    }
}

Я получаю этот выход

A1B1C1
A2B2C2
A3B3C3

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

попытка выполнить аналогичное вычисление на последних трех элементах с помощью skip() метод, показывает другое поведение: это

public class Main {
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6,7,8,9)
        .peek(x->System.out.print("nA"+x))
        .skip(6)
        .peek(x->System.out.print("B"+x))
        .forEach(x->System.out.print("C"+x));
    }
}

выходы это

A1
A2
A3
A4
A5
A6
A7B7C7
A8B8C8
A9B9C9

почему, в данном случае, действия A1 до A6 исполняются? Это должно быть как-то связано с тем, что ограничения это промежуточная операция с коротким замыканием, а пропустить нет, но я не понимаю практического значения этого свойства. Это просто, что "каждое действие перед пропустить выполняется пока не все до ограничения это"?

182   4  

4 ответов:

то, что у вас есть здесь два потока трубопроводов.

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

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

терминальные деятельности обычно "нетерпеливы". То есть они запрашивают все элементы в потоке, которые необходимы для их завершения.

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

имея это в виду, давайте посмотрим, что у нас с первого конвейера:

Stream.of(1,2,3,4,5,6,7,8,9)
        .peek(x->System.out.print("\nA"+x))
        .limit(3)
        .peek(x->System.out.print("B"+x))
        .forEach(x->System.out.print("C"+x));

Итак,forEach is спрашиваю по первому пункту. Это означает "B"peek нужен элемент, и спрашивает limit выходной поток для него, что означает limit нужно будет спросить "А"peek, которая идет к источнику. Элемент дается, и идет весь путь до forEach, и вы получите свою первую строку:

A1B1C1

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

таким образом, он не просит "a" заглянуть для другого элемента. Это сразу указывает на то, что его элементы исчерпаны, и, таким образом, больше не выполняются действия и forEach завершается.

что происходит во втором трубопроводе?

    Stream.of(1,2,3,4,5,6,7,8,9)
    .peek(x->System.out.print("\nA"+x))
    .skip(6)
    .peek(x->System.out.print("B"+x))
    .forEach(x->System.out.print("C"+x));

опять forEach просит первый пункт. Это распространяется обратно. Но когда он доберется до skip, он знает, что ему надо попросите 6 пунктов от его вверх по течению, прежде чем он может пройти один вниз по течению. Поэтому он делает запрос вверх по течению от "A"peek, потребляет его, не передавая его вниз по течению, делает еще один запрос, и так далее. Таким образом, " a " peek получает 6 запросов на элемент и производит 6 отпечатков, но эти элементы не передаются вниз.

A1
A2
A3
A4
A5
A6

по 7-му запросу, сделанному skip, элемент передается вниз к" B " peek и от него к forEach, поэтому полная печать сделано:

A7B7C7

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

беглая нотация потокового конвейера-это то, что вызывает эту путаницу. Подумайте об этом так:

limit(3)

все конвейерные операции оцениваются лениво, кроме forEach(), который является терминала, что триггеры "оформление газопровода".

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

Stream<Integer> s1 = Stream.of(1,2,3,4,5,6,7,8,9);
Stream<Integer> s2 = s1.peek(x->System.out.print("\nA"+x));
Stream<Integer> s3 = s2.limit(3);
Stream<Integer> s4 = s3.peek(x->System.out.print("B"+x));

s4.forEach(x->System.out.print("C"+x));
  • s1 содержит 9 разных Integer значения.
  • s2 считывает все значения, которые передают его и печатает их.
  • s3 передает первые 3 значения в s4 и прерывает конвейер после третьего значения. Никакие дополнительные значения не создаются s3. это не означает, что больше нет значений трубопровод. s2 будет по-прежнему производить (и печатать) больше значений, но никто не запрашивает эти значения и, таким образом, выполнение останавливается.
  • s4 снова заглядывает на все значения, которые передают его и печатает их.
  • forEach потребляет и печатает то, что s4 переходит к нему.

подумайте об этом таким образом. Весь поток совершенно ленив. Только работа терминала активно дерет новые значения из трубопровода. После вытащил 3 значения из s4 <- s3 <- s2 <- s1,s3 больше не будет производить новые ценности и он больше не будет тянуть любые значения из s2 <- s1. В то время как s1 -> s2 все равно смог бы произвести 4-9, эти значения просто никогда не вытаскиваются из конвейера и, следовательно, никогда не печатаются s2.

skip(6)

с skip() то же самое происходит:

Stream<Integer> s1 = Stream.of(1,2,3,4,5,6,7,8,9);
Stream<Integer> s2 = s1.peek(x->System.out.print("\nA"+x));
Stream<Integer> s3 = s2.skip(6);
Stream<Integer> s4 = s3.peek(x->System.out.print("B"+x));

s4.forEach(x->System.out.print("C"+x));
  • s1 содержит 9 разных Integer значения.
  • s2 заглядывает на все значения вот передают его и печатают их.
  • s3 потребляет первые 6 значений, "прогуливаешь", что означает, что первые 6 значений не передаются в s4, только последующие значения.
  • s4 снова заглядывает на все значения, которые передают его и печатает их.
  • forEach потребляет и печатает то, что s4 переходит к нему.

главное здесь то, что s2 не знает о пропуске оставшегося трубопровода любое значение. s2 считывает все значения независимо от того, что произойдет после этого.

еще пример:

рассмотрим этот трубопровод,, который указан в этом блоге

IntStream.iterate(0, i -> ( i + 1 ) % 2)
         .distinct()
         .limit(10)
         .forEach(System.out::println);

когда вы выполняете выше, программа не будет остановлена. Зачем? Потому что:

IntStream i1 = IntStream.iterate(0, i -> ( i + 1 ) % 2);
IntStream i2 = i1.distinct();
IntStream i3 = i2.limit(10);

i3.forEach(System.out::println);

что означает:

  • i1 генерирует бесконечное количество переменных значений:0,1,0,1, 0,1, ...
  • i2 потребляет все значения, которые встречались ранее, передавая только "новый" значения, т. е. есть в общей сложности 2 значения, выходящие из i2.
  • i3 передает 10 значений, затем останавливается.

этот алгоритм никогда не остановится, потому что i3 ждет i2 чтобы получить еще 8 значений после 0 и 1, но эти значения никогда не появляются, в то время как i1 никогда не останавливается значения подачи в i2.

не имеет значения, что в какой-то момент в конвейере было произведено более 10 значений. Все, что имеет значение, это то, что i3 никогда не видел эти 10 значений.

чтобы ответить на ваш вопрос:

это просто, что "каждое действие перед пропуском выполняется, а не все до предела"?

Неа. Все операции Перед либо skip() или limit() выполнены. В обоих расстрелы, вы получите A1 -A3. Но limit() возможно короткое замыкание трубопровода, прерывающее потребление значения после того, как произошло событие интереса (достигнут предел).

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

про ограничение(3), это операция короткого замыкания, что имеет смысл, потому что думать об этом, независимо от операции до и после the limit, имея ограничение в потоке остановит итерацию после получения n элементов до предельная операция, но это не значит что только n элементов потока будут обработаны. Возьмите эту другую операцию потока для примера

public class App 
{
    public static void main(String[] args) {
        Stream.of(1,2,3,4,5,6,7,8,9)
        .peek(x->System.out.print("\nA"+x))
        .filter(x -> x%2==0)
        .limit(3)
        .peek(x->System.out.print("B"+x))
        .forEach(x->System.out.print("C"+x));
    }
}

выводит

A1
A2B2C2
A3
A4B4C4
A5
A6B6C6

которые кажутся правильными, потому что limit ожидает, что 3 элемента потока пройдут через цепочку операций, хотя обрабатываются 6 элементов потока.

все потоки основаны на сплитераторах, которые имеют в основном две операции: advance (перемещение вперед одного элемента, аналогичного итератору) и split (разделение себя в произвольном положении, которое подходит для параллельной обработки). Вы можете прекратить принимать входные элементы в любой момент, когда вам нравится (что делается limit), но вы не можете просто перейти в произвольную позицию (нет такой операции в Spliterator интерфейс). Таким образом skip операция должна фактически прочитать первые элементы из источник просто игнорирует их. Обратите внимание, что в некоторых случаях вы можете выполнить прыжок с парашютом:

List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);

list.stream().skip(3)... // will read 1,2,3, but ignore them
list.subList(3, list.size()).stream()... // will actually jump over the first three elements
    Ничего не найдено.

Добавить ответ:
Отменить.