JavaJava 8 数据流教程

Java 8 数据流教程

原文:Java 8 Stream
Tutorial

译者:飞龙

协议:CC BY-NC-SA
4.0

此示例驱动之学科是Java8数据流(Stream)的深入总结。当自家第一次等探望StreamAPI时,我死纳闷,因为它们听起来和Java
IO的InputStream
OutputStream如出一辙。但是Java8的数据流是一心两样之物。数据流是单体(Monad),并且以Java8部数式编程中打及要作用。

于函数式编程中,单体是一个结构,表示定义也步骤序列的计。单体结构的类型定义了其对链式操作,或富有相同档次的嵌套函数的意义。

斯科目教受您什么用Java8屡据流,以及怎样以不同类别的可用之多少流操作。你用会见学到拍卖程序以及流操作的次如何影响运行时效率。这个科目也会见详细讲解更加强硬的流操作,reducecollectflatMap。最后,这个教程会深入探讨并行流。

倘你还未熟悉Java8底lambda表达式,函数式接口及道引用,你也许用在开始即同样章之前,首先看我的Java8教程。

更新 –
我本着编写用于浏览器的Java8数流API的JavaScript实现。如果您对这感兴趣,请在Github上看Stream.js。非常盼望您的举报。

数据流如何行事

数据流表示元素的行列,并支持不同种类之操作来推行元素上之算计:

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
    .stream()
    .filter(s -> s.startsWith("c"))
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);

// C1
// C2

数据流操作要么是搭操作,要么是休操作。衔接操作返回数据流,所以我们得以管多个连片操作不动分号来链接到一起。终止操作无返回值,或者返回一个非是流动的结果。在地方的例证中,filtermapsorted且是连操作,而forEach是已操作。列表上之兼具流式操作请见数据流的Javadoc。你以上头例子中见到的这种数据流的链式操作为给作操作流程。

大部多少流操作都领部分lambda表达式参数,函数式接口用来指定操作的现实表现。这些操作的大多数须要是不管打扰又是随便状态的。它们是啊意思呢?

当一个函数不改数据流的脚数据源,它就是是凭干扰的。例如,在地方的事例中,没有任何lambda表达式通过长或删除集合元素修改myList

当一个函数的操作的履行是显而易见的,它就是是不论状态的。例如,在方的事例中,没有任何lambda表达式依赖让表面作用域中任何在操作过程中可是更换的变量或状态。

数据流的异门类

数据流可以由多数据源创建,尤其是聚众。ListSet支撑新章程stream()

parallelStream(),来创造串行流或连行流。并行流能够在差不多只线程上推行操作,它们会当后来的节中提到。我们今天来瞧串行流:

Arrays.asList("a1", "a2", "a3")
    .stream()
    .findFirst()
    .ifPresent(System.out::println);  // a1

以目标列表上调用stream()方法会返回一个习以为常的对象流。但是咱无待创造一个凑来创造数据流,就像下那样:

Stream.of("a1", "a2", "a3")
    .findFirst()
    .ifPresent(System.out::println);  // a1

假定以Stream.of(),就可起同文山会海对象引用中创造数据流。

除了一般的目标数据流,Java8尚自带了特类别的流淌,用于拍卖为主数据列intlong
double。你也许已蒙到了她是IntStreamLongStream
DoubleStream

IntStream得行使IntStream.range()轮换通常的for循环:

IntStream.range(1, 4)
    .forEach(System.out::println);

// 1
// 2
// 3

不无这些骨干数据流都像平常的靶子数据流一样,但有一部分不同。基本的数据流使用特殊的lambda表达式,例如,IntFunction而不是FunctionIntPredicate而不是Predicate。而且着力数据流支持额外的联谊终止操作sum()average()

Arrays.stream(new int[] {1, 2, 3})
    .map(n -> 2 * n + 1)
    .average()
    .ifPresent(System.out::println);  // 5.0

偶然用拿普通的靶子数据流转换为主导数据流,或者相反。出于这种目的,对象数据流支持特别的照射操作mapToInt()mapToLong()
mapToDouble()

Stream.of("a1", "a2", "a3")
    .map(s -> s.substring(1))
    .mapToInt(Integer::parseInt)
    .max()
    .ifPresent(System.out::println);  // 3

核心数据流可以透过maoToObj()易为对象数据流:

IntStream.range(1, 4)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3

下面是构成示例:浮点数据流首先映射为整数数据流,之后映射为字符串的对象数据流:

Stream.of(1.0, 2.0, 3.0)
    .mapToInt(Double::intValue)
    .mapToObj(i -> "a" + i)
    .forEach(System.out::println);

// a1
// a2
// a3

拍卖顺序

既然如此我们就了解了怎样创建并以不同门类之数据流,让咱深切摸底多少流操作在背后如何实施吧。

连片操作的一个重大特色即是延迟性。观察下没有停歇操作的事例:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    });

履这段代码时,不为控制台打印任何东西。这是盖接操作就当悬停操作调用时为实践。

深受咱们通过丰富终止操作forEach来扩充这个例子:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return true;
    })
    .forEach(s -> System.out.println("forEach: " + s));

施行这段代码会获得如下输出:

filter:  d2
forEach: d2
filter:  a2
forEach: a2
filter:  b1
forEach: b1
filter:  b3
forEach: b3
filter:  c
forEach: c

结果的次第可能突然。原始之方法会在数据流的有因素上,一个衔接一个地水平执行有操作。但是每个元素于调整用链上垂直运动。第一只字符串"d2"第一通过filter然后是forEach,执行了晚才开拍卖第二单字符串"a2"

这种作为足以减少每个元素上所执行的实际操作数量,就比如咱以产单例子中来看的那样:

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .anyMatch(s -> {
        System.out.println("anyMatch: " + s);
        return s.startsWith("A");
    });

// map:      d2
// anyMatch: D2
// map:      a2
// anyMatch: A2

倘提供的多寡元素满足了谓词,anyMatch操作就会见回来true。对于第二单传递"A2"的要素,它的结果为真正。由于数据流的链式调用是垂直执行之,map这里只需要履行两次于。所以map会执行尽可能少的次数,而未是拿装有因素都映射一遍。

为什么顺序如此重大

脚的事例由个别独接入操作mapfilter,以及一个止操作forEach做。让咱们还来瞧这些操作如何实施:

Stream.of("d2", "a2", "b1", "b3", "c")
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("A");
    })
    .forEach(s -> System.out.println("forEach: " + s));

// map:     d2
// filter:  D2
// map:     a2
// filter:  A2
// forEach: A2
// map:     b1
// filter:  B1
// map:     b3
// filter:  B3
// map:     c
// filter:  C

即使如您或许猜到的那么,mapfilter会见针对根集合的每个字符串调用五不善,而forEach单见面调用一糟糕。

假设我们调整操作顺序,将filter活动至调用链的头,就好大幅度减少操作的施行次数:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

// filter:  d2
// filter:  a2
// map:     a2
// forEach: A2
// filter:  b1
// filter:  b3
// filter:  c

现在,map但见面调用一坏,所以操作流程对于再次多之输入元素会履更快。在整合复杂的方式链时,要铭记在心这一点。

为我们经过添加额外之法子sorted来扩张上面的例证:

Stream.of("d2", "a2", "b1", "b3", "c")
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s\n", s1, s2);
        return s1.compareTo(s2);
    })
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

排序是一样近似非常的属操作。它是发状态的操作,因为你要在拍卖着保留状态来对聚集中的因素排序。

尽之例子会落如下输入:

sort:    a2; d2
sort:    b1; a2
sort:    b1; d2
sort:    b1; a2
sort:    b3; b1
sort:    b3; d2
sort:    c; b3
sort:    c; d2
filter:  a2
map:     a2
forEach: A2
filter:  b1
filter:  b3
filter:  c
filter:  d2

第一,排序操作以漫天输入集合上实施。也就是说,sorted因为水平方式实行。所以这边sorted对输入集合中每个元素的余成调用了八坏。

咱同好经过重排调用链来优化性能:

Stream.of("d2", "a2", "b1", "b3", "c")
    .filter(s -> {
        System.out.println("filter: " + s);
        return s.startsWith("a");
    })
    .sorted((s1, s2) -> {
        System.out.printf("sort: %s; %s\n", s1, s2);
        return s1.compareTo(s2);
    })
    .map(s -> {
        System.out.println("map: " + s);
        return s.toUpperCase();
    })
    .forEach(s -> System.out.println("forEach: " + s));

// filter:  d2
// filter:  a2
// filter:  b1
// filter:  b3
// filter:  c
// map:     a2
// forEach: A2

此事例中sorted世代不见面调用,因为filter把输入集合减少及只有生一个要素。所以对于再次要命的输入集合会极大提升性。

复用数据流

Java8的数额流不克吃复用。一旦您调用了其余终止操作,数据流就关门了:

Stream<String> stream =
    Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> s.startsWith("a"));

stream.anyMatch(s -> true);    // ok
stream.noneMatch(s -> true);   // exception

当一如既往数量流上,在anyMatch从此以后调用noneMatch会晤出下的大:

java.lang.IllegalStateException: stream has already been operated upon or closed
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
    at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
    at com.winterbe.java8.Streams5.test7(Streams5.java:38)
    at com.winterbe.java8.Streams5.main(Streams5.java:28)

使战胜这个界定,我们需要吗每个我们纪念要实践之停止操作创建新的多寡流调用链。例如,我们创建一个数据流供应器,来构建新的数据流,并且安装好有接操作:

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));

streamSupplier.get().anyMatch(s -> true);   // ok
streamSupplier.get().noneMatch(s -> true);  // ok

每次对get()的调用都组织了一个初的数据流,我们将该保存来调用终止操作。

高等操作

数据流执行大气的不同操作。我们曾经了解了有些最为重大之操作,例如filtermap。我用她留您来探索具有其他的可用操作(请见数据流的Javadoc)。下面被我们深刻摸底部分再度扑朔迷离的操作:collectflatMapreduce

就同一节的多数代码示例使用下的Person列表来演示:

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public String toString() {
        return name;
    }
}

List<Person> persons =
    Arrays.asList(
        new Person("Max", 18),
        new Person("Peter", 23),
        new Person("Pamela", 23),
        new Person("David", 12));

collect

collect大凡深实用之停操作,将流中的元素存放于不同档次的结果被,例如ListSet或者Mapcollect领收集器(Collector),它由四独例外之操作成:供应器(supplier)、累加器(accumulator)、组合器(combiner)和终止器(finisher)。这当开头任起十分复杂,但是Java8经内置的Collectors接近支持多置的收集器。所以对多数宽广操作,你并不需要自己实现收集器。

给我们为一个坏广泛的用例来起:

List<Person> filtered =
    persons
        .stream()
        .filter(p -> p.name.startsWith("P"))
        .collect(Collectors.toList());

System.out.println(filtered);    // [Peter, Pamela]

虽像而看底那么,它非常简单,只是从流的元素被组织了一个列表。如果要盖Set来替代List,只需要动用Collectors.toSet()就好了。

下的例子按照年对负有人数展开分组:

Map<Integer, List<Person>> personsByAge = persons
    .stream()
    .collect(Collectors.groupingBy(p -> p.age));

personsByAge
    .forEach((age, p) -> System.out.format("age %s: %s\n", age, p));

// age 18: [Max]
// age 23: [Peter, Pamela]
// age 12: [David]

收集器十分灵活。你为堪在流的因素上实行聚合,例如,计算有所人之平均年龄:

Double averageAge = persons
    .stream()
    .collect(Collectors.averagingInt(p -> p.age));

System.out.println(averageAge);     // 19.0

设您针对还多统计学方法感兴趣,概要收集器返回一个特殊的内置概要统计对象,所以我们好简简单单算最小年龄、最充分年纪、算术平均年龄、总跟和数目。

IntSummaryStatistics ageSummary =
    persons
        .stream()
        .collect(Collectors.summarizingInt(p -> p.age));

System.out.println(ageSummary);
// IntSummaryStatistics{count=4, sum=76, min=12, average=19.000000, max=23}

脚的事例将具有人连续为一个字符串:

String phrase = persons
    .stream()
    .filter(p -> p.age >= 18)
    .map(p -> p.name)
    .collect(Collectors.joining(" and ", "In Germany ", " are of legal age."));

System.out.println(phrase);
// In Germany Max and Peter and Pamela are of legal age.

接连收集器接受分隔符,以及可选的前缀和后缀。

为了将数据流中的因素转换为照射,我们需要依赖定键和价值什么吃射。要记住键必须是唯一的,否则会抛出IllegalStateException老大。你可以选取传递一个合函数作为额外的参数来避免此非常。

既然我们理解了有无限有力的放置收集器,让我们来尝试构建协调之特殊收集器吧。我们愿意将流中的富有人数更换为一个字符串,包含有大写的名目,并坐|分割。为了好其,我们由此Collector.of()开创了一个初的收集器。我们用传递一个收集器的季独组成部分:供应器、累加器、组合器和终止器。

Collector<Person, StringJoiner, String> personNameCollector =
    Collector.of(
        () -> new StringJoiner(" | "),          // supplier
        (j, p) -> j.add(p.name.toUpperCase()),  // accumulator
        (j1, j2) -> j1.merge(j2),               // combiner
        StringJoiner::toString);                // finisher

String names = persons
    .stream()
    .collect(personNameCollector);

System.out.println(names);  // MAX | PETER | PAMELA | DAVID

由Java中的字符串是不可变的,我们要一个助手类StringJointer。让采访器构造我们的字符串。供应器最初步以相应的隔符构造了这么一个StringJointer。累加器用于将每个人的生写名称加到StringJointer倍受。组合器知道如何把少单StringJointer统一为一个。最后一步,终结器从StringJointer结构出意料的字符串。

flatMap

咱俩曾经了解了哪通过行使map操作,将流中的对象转换为另外一样种档次。map有时坏受限,因为每个对象只能照射为一个旁对象。但怎么自盼望将一个靶转换为多单或零个其他对象啊?flatMap这时便会派上用场。

flatMap以流中的每个元素,转换为另外对象的流淌。所以每个对象见面给更换为零个、一个要多个其他对象,以流的样式返回。这些流动的内容后会放上flatMap所返的流中。

当我们了解flatMap什么样运用之前,我们需要相应的项目体系:

class Foo {
    String name;
    List<Bar> bars = new ArrayList<>();

    Foo(String name) {
        this.name = name;
    }
}

class Bar {
    String name;

    Bar(String name) {
        this.name = name;
    }
}

脚,我们用我们温馨之有关流的学问来实例化一些目标:

List<Foo> foos = new ArrayList<>();

// create foos
IntStream
    .range(1, 4)
    .forEach(i -> foos.add(new Foo("Foo" + i)));

// create bars
foos.forEach(f ->
    IntStream
        .range(1, 4)
        .forEach(i -> f.bars.add(new Bar("Bar" + i + " <- " + f.name))));

现咱们所有了含有三只foo的列表,每个都含有三个bar

flatMap经受返回对象流的函数。所以为了处理每个foo上的bar目标,我们用传递相应的函数:

foos.stream()
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

// Bar1 <- Foo1
// Bar2 <- Foo1
// Bar3 <- Foo1
// Bar1 <- Foo2
// Bar2 <- Foo2
// Bar3 <- Foo2
// Bar1 <- Foo3
// Bar2 <- Foo3
// Bar3 <- Foo3

譬如说你看到的那样,我们成地用含有三只foo靶中的流转换为涵盖九个bar对象的流动。

终极,上面的代码示例可以简化为流式操作的单一流水线:

IntStream.range(1, 4)
    .mapToObj(i -> new Foo("Foo" + i))
    .peek(f -> IntStream.range(1, 4)
        .mapToObj(i -> new Bar("Bar" + i + " <- " f.name))
        .forEach(f.bars::add))
    .flatMap(f -> f.bars.stream())
    .forEach(b -> System.out.println(b.name));

flatMap为可用来Java8引入的Optional类。OptionalflatMap操作返回一个Optional还是其他类别的目标。所以其好用于避免烦人的null检查。

考虑像这么还复杂的层次结构:

class Outer {
    Nested nested;
}

class Nested {
    Inner inner;
}

class Inner {
    String foo;
}

以处理外层示例上之内层字符串foo,你需要添加多个null检查来避免地下的NullPointerException

Outer outer = new Outer();
if (outer != null && outer.nested != null && outer.nested.inner != null) {
    System.out.println(outer.nested.inner.foo);
}

好运用OptionalflatMap操作来好同样之所作所为:

Optional.of(new Outer())
    .flatMap(o -> Optional.ofNullable(o.nested))
    .flatMap(n -> Optional.ofNullable(n.inner))
    .flatMap(i -> Optional.ofNullable(i.foo))
    .ifPresent(System.out::println);

假使有的话,每个flatMap的调用都见面回来预期目标的Optional包装,否则为nullOptional包装。

reduce

归约操作将有着流中的元素构成为单纯结果。Java8支撑三栽不同门类的reduce道。第一种将流中的元素归约为流中的一个素。让我们看我们什么下这个艺术来计算起无限总的丁:

persons
    .stream()
    .reduce((p1, p2) -> p1.age > p2.age ? p1 : p2)
    .ifPresent(System.out::println);    // Pamela

reduce方式接受BinaryOperator积累函数。它实在是简单个操作数类型相同的BiFunctionBiFunction就像是Function,但是接受两独参数。示例中的函数比较简单个人的年华,来回到年龄比生之丁。

第二个reduce措施接受一个新开始值,和一个BinaryOperator累加器。这个法子好用于自流中的另Person靶吃结构带有聚合后称及年的初Person对象。

Person result =
    persons
        .stream()
        .reduce(new Person("", 0), (p1, p2) -> {
            p1.age += p2.age;
            p1.name += p2.name;
            return p1;
        });

System.out.format("name=%s; age=%s", result.name, result.age);
// name=MaxPeterPamelaDavid; age=76

第三个reduce目标接受三单参数:初开始值,BiFunction累加器和BinaryOperator路的组合器函数。由于初始值的花色不必然为Person,我们得以利用此归约函数来算有所人之春秋总和。:

Integer ageSum = persons
    .stream()
    .reduce(0, (sum, p) -> sum += p.age, (sum1, sum2) -> sum1 + sum2);

System.out.println(ageSum);  // 76

若可见见结果是76。但是背后有了哟?让我们透过添加一些调节输出来扩展上面的代码:

Integer ageSum = persons
    .stream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
            return sum1 + sum2;
        });

// accumulator: sum=0; person=Max
// accumulator: sum=18; person=Peter
// accumulator: sum=41; person=Pamela
// accumulator: sum=64; person=David

你可以观看,累加器函数做了富有工作。它首先以初始值0及第一私Max来调用累加器。接下来的老三步着sum会见不停加码,直到76。

等一下。好像组合器从来没有调用了?以互方式履行同一之流会揭开这个神秘:

Integer ageSum = persons
    .parallelStream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s\n", sum, p);
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s\n", sum1, sum2);
            return sum1 + sum2;
        });

// accumulator: sum=0; person=Pamela
// accumulator: sum=0; person=David
// accumulator: sum=0; person=Max
// accumulator: sum=0; person=Peter
// combiner: sum1=18; sum2=23
// combiner: sum1=23; sum2=12
// combiner: sum1=41; sum2=35

这流的并行执行行为会了不同。现在其实调用了组合器。由于累加器被并行调用,组合器需要用于计算部分累加值的总和。

下同样节省咱们会深入了解并行流。

并行流

注得并行执行,在大量输入元素上得提升运作时之性。并行流使用公共的ForkJoinPool,由ForkJoinPool.commonPool()术供。底层线程池的大大小小最深吗五单线程
— 取决于CPU的情理核数。

ForkJoinPool commonPool = ForkJoinPool.commonPool();
System.out.println(commonPool.getParallelism());    // 3

以自己的机械及,公共池默认初始化为3。这个价值好通过安装下列JVM参数来增减:

-Djava.util.concurrent.ForkJoinPool.common.parallelism=5

集合支持parallelStream()办法来创造元素的连行流。或者你可于曾经是的数目流上调用衔接方式parallel(),将串行流转换为连行流。

以描述并行流的行行为,下面的例子向sout打印了当前线程的音。

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

经分析调试输出,我们好对孰线程用于实践流式操作有更深切之敞亮:

filter:  b1 [main]
filter:  a2 [ForkJoinPool.commonPool-worker-1]
map:     a2 [ForkJoinPool.commonPool-worker-1]
filter:  c2 [ForkJoinPool.commonPool-worker-3]
map:     c2 [ForkJoinPool.commonPool-worker-3]
filter:  c1 [ForkJoinPool.commonPool-worker-2]
map:     c1 [ForkJoinPool.commonPool-worker-2]
forEach: C2 [ForkJoinPool.commonPool-worker-3]
forEach: A2 [ForkJoinPool.commonPool-worker-1]
map:     b1 [main]
forEach: B1 [main]
filter:  a1 [ForkJoinPool.commonPool-worker-3]
map:     a1 [ForkJoinPool.commonPool-worker-3]
forEach: A1 [ForkJoinPool.commonPool-worker-3]
forEach: C1 [ForkJoinPool.commonPool-worker-2]

尽管如您看来的那样,并行流使用了有着国有的ForkJoinPool被之可用线程来实施流式操作。在连接的运转面临输出可能截然不同,因为所运用的特定线程是未特定的。

于咱们由此添加额外之流式操作sort来扩大这个示例:

Arrays.asList("a1", "a2", "b1", "c2", "c1")
    .parallelStream()
    .filter(s -> {
        System.out.format("filter: %s [%s]\n",
            s, Thread.currentThread().getName());
        return true;
    })
    .map(s -> {
        System.out.format("map: %s [%s]\n",
            s, Thread.currentThread().getName());
        return s.toUpperCase();
    })
    .sorted((s1, s2) -> {
        System.out.format("sort: %s <> %s [%s]\n",
            s1, s2, Thread.currentThread().getName());
        return s1.compareTo(s2);
    })
    .forEach(s -> System.out.format("forEach: %s [%s]\n",
        s, Thread.currentThread().getName()));

结果起初可能比较奇怪:

filter:  c2 [ForkJoinPool.commonPool-worker-3]
filter:  c1 [ForkJoinPool.commonPool-worker-2]
map:     c1 [ForkJoinPool.commonPool-worker-2]
filter:  a2 [ForkJoinPool.commonPool-worker-1]
map:     a2 [ForkJoinPool.commonPool-worker-1]
filter:  b1 [main]
map:     b1 [main]
filter:  a1 [ForkJoinPool.commonPool-worker-2]
map:     a1 [ForkJoinPool.commonPool-worker-2]
map:     c2 [ForkJoinPool.commonPool-worker-3]
sort:    A2 <> A1 [main]
sort:    B1 <> A2 [main]
sort:    C2 <> B1 [main]
sort:    C1 <> C2 [main]
sort:    C1 <> B1 [main]
sort:    C1 <> C2 [main]
forEach: A1 [ForkJoinPool.commonPool-worker-1]
forEach: C2 [ForkJoinPool.commonPool-worker-3]
forEach: B1 [main]
forEach: A2 [ForkJoinPool.commonPool-worker-2]
forEach: C1 [ForkJoinPool.commonPool-worker-1]

sort在押起才以主线程上串行执行。实际上,并行流上的sort每当偷偷下了Java8中新的方法Arrays.parallelSort()。如javadoc所说,这个方法会参照数据长度来控制为串行或相互来施行。

倘若指定数量的尺寸小于最小粒度,它采取相应的Arrays.sort主意来排序。

返上同样节约被reduce的事例。我们早就意识了组合器函数只在连行流中调用,而非以错行流中调用。让我们来观实际上干到哪个线程:

List<Person> persons = Arrays.asList(
    new Person("Max", 18),
    new Person("Peter", 23),
    new Person("Pamela", 23),
    new Person("David", 12));

persons
    .parallelStream()
    .reduce(0,
        (sum, p) -> {
            System.out.format("accumulator: sum=%s; person=%s [%s]\n",
                sum, p, Thread.currentThread().getName());
            return sum += p.age;
        },
        (sum1, sum2) -> {
            System.out.format("combiner: sum1=%s; sum2=%s [%s]\n",
                sum1, sum2, Thread.currentThread().getName());
            return sum1 + sum2;
        });

控制台的输出表明,累加器和组合器都于有可用之线程上并行执行:

accumulator: sum=0; person=Pamela; [main]
accumulator: sum=0; person=Max;    [ForkJoinPool.commonPool-worker-3]
accumulator: sum=0; person=David;  [ForkJoinPool.commonPool-worker-2]
accumulator: sum=0; person=Peter;  [ForkJoinPool.commonPool-worker-1]
combiner:    sum1=18; sum2=23;     [ForkJoinPool.commonPool-worker-1]
combiner:    sum1=23; sum2=12;     [ForkJoinPool.commonPool-worker-2]
combiner:    sum1=41; sum2=35;     [ForkJoinPool.commonPool-worker-2]

总的说来,并行流对富有大量输入元素的数据流具有巨大的习性提升。但是要是记住有连行流的操作,例如reducecollect消分外的计算(组合操作),这在串行执行时并不需要。

此外我们已经了解,所有并行流操作都共享相同的JVM相关的公家ForkJoinPool。所以您恐怕得避免实现以磨蹭而卡的流式操作,因为她可能会见拖慢而用被严重依赖并行流的别一些。

暨是结束

本人之Java8往往据流编程课程就这个已。如果您对深入了解Java8数量流感兴趣,我望而推荐数据流的Javadoc。如果你指望学到再也多根机制,你或需要阅读Martin
Fowler关于集结流水线的文章。

设你针对JavaScript也感谢兴趣,你也许希望看同样押Stream.js

一个Java8数目流API的JavaScript实现。你也恐怕希望阅读我的Java8显著教程,和我的Java8Nashron教程。

自我希望而见面欣赏这篇稿子。如果你发出另的题目都好以脚评论或者经
Twitter
给我回复。

祝编程愉快!

相关文章