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 исполняются? Это должно быть как-то связано с тем, что ограничения это промежуточная операция с коротким замыканием, а пропустить нет, но я не понимаю практического значения этого свойства. Это просто, что "каждое действие перед пропустить выполняется пока не все до ограничения это"?
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));Итак,
forEachis спрашиваю по первому пункту. Это означает "B"peekнужен элемент, и спрашиваетlimitвыходной поток для него, что означаетlimitнужно будет спросить "А"peek, которая идет к источнику. Элемент дается, и идет весь путь доforEach, и вы получите свою первую строку:A1B1C1The
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
Comments