sOlOHsU's Blogβ

灯火阑珊处

Java 8 简明教程(7): Streams

Streams

java.util.Stream表示一个多元素的数组,在上面可以进行一项或者多项操作。Stream操作分为两种,一种是intermediate操作,一种是terminal操作。terminal操作返回的是某一特定类型的结果,而intermediate操作返回的stream对象本身,这样以来我们就可以在一行中将多个方法链接起来。Streams创建的时候必须指定一个source,例如java.util.Collection中的lists或者sets(maps是不被支持的)。Streams操作既可以是串行的也可以是并行的。

首先来看一个串行执行streams操作的例子,这个例子中的source是一个string数组:

1
2
3
4
5
6
7
8
9
 List<String> stringCollection = new ArrayList<>();
  stringCollection.add("ddd2");
  stringCollection.add("aaa2");
  stringCollection.add("bbb1");
  stringCollection.add("aaa1");
  stringCollection.add("bbb3");
  stringCollection.add("ccc");
  stringCollection.add("bbb2");
  stringCollection.add("ddd1");

Java 8对Collections进行了扩展,我们可以直接调用Collection.stream()或者Collection.parallelStream()来创建stream。下面是一些常见的stream操作。

Filter

Filter接受一个判断条件并对stream中的所有元素进行过滤操作。这是一个intermediate操作,我们可以在它的返回结果上继续调用其他的stream操作(比如这里的forEach)。ForEach接受一个「消费者」,这个「消费者」对经过过滤后的stream中的每一个元素进行「消费」。ForEach是一个terminal操作。它的返回值是void,所以我们无法再继续调用其他操作了。

1
2
3
4
5
6
 stringCollection
      .stream()
      .filter((s) -> s.startsWith("a"))
      .forEach(System.out::println);
  
  // "aaa2", "aaa1"

Sorted

Sorted是一个intermediate操作,它返回经过排序后的stream。默认的排序是按照自然顺序,想要实现自定义的比较方式需要向sorted方法传递一个自定义的Comparator。

1
2
3
4
5
6
7
 stringCollection
      .stream()
      .sorted()
      .filter((s) -> s.startsWith("a"))
      .forEach(System.out::println);
  
  // "aaa1", "aaa2"

需要注意的是,sorted仅创建了当前stream经过排序后的一个视图,而没有真正修改后方的collection。stringCollection中的顺序依然保持不变:

1
2
 System.out.println(stringCollection);
  // ddd2, aaa2, bbb1, aaa1, bbb3, ccc, bbb2, ddd1

Map

map也是一个intermediate操作,它使用给定的函数将每个元素转换成另一个对象。下面的例子中将每一个字符串进行了upper操作。另外,也可以使用map将对象转换成另一种类型。得到的stream的泛型取决于传递给map的函数的返回值类型。

1
2
3
4
5
6
7
 stringCollection
      .stream()
      .map(String::toUpperCase)
      .sorted((a, b) -> b.compareTo(a))
      .forEach(System.out::println);
  
  // "DDD2", "DDD1", "CCC", "BBB3", "BBB2", "AAA2", "AAA1"

Match

各式各样的匹配操作可以用来检查一个特定的谓词表达式是否与stream相匹配。所有的匹配操作都是terminal类型的,并且返回的是boolean类型的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
 boolean anyStartsWithA =
      stringCollection
          .stream()
          .anyMatch((s) -> s.startsWith("a"));
  
  System.out.println(anyStartsWithA);      // true
  
  boolean allStartsWithA =
      stringCollection
          .stream()
          .allMatch((s) -> s.startsWith("a"));
  
  System.out.println(allStartsWithA);      // false
  
  boolean noneStartsWithZ =
      stringCollection
          .stream()
          .noneMatch((s) -> s.startsWith("z"));
  
  System.out.println(noneStartsWithZ);      // true

Count

count是一个terminal操作,它以long类型返回stream中元素的个数。

1
2
3
4
5
6
7
 long startsWithB =
      stringCollection
          .stream()
          .filter((s) -> s.startsWith("b"))
          .count();
  
  System.out.println(startsWithB);    // 3

Reduce

reduce是一个terminal操作,它使用给定的函数对stream中的元素进行reduction操作。返回的是一个Optional对象,其中存放着reduce之后的值。

1
2
3
4
5
6
7
8
 Optional<String> reduced =
      stringCollection
          .stream()
          .sorted()
          .reduce((s1, s2) -> s1 + "#" + s2);
  
  reduced.ifPresent(System.out::println);
  // "aaa1#aaa2#bbb1#bbb2#bbb3#ccc#ddd1#ddd2"

Parallel Streams

上面提到了streams分为串行和并行两类。串行streams中的操作都是在一个线程中执行的,而并行sterams上的操作是在多个线程中并发执行的。

下面的例子向我们展示了使用并行streams来提升性能是如此的简单。

首先创建一个没有重复元素的大数组。

1
2
3
4
5
6
 int max = 1000000;
  List<String> values = new ArrayList<>(max);
  for (int i = 0; i < max; i++) {
      UUID uuid = UUID.randomUUID();
      values.add(uuid.toString());
  }

接下来我们来测一下对这个数组的stream进行排序所消耗的时间。

串行排序

1
2
3
4
5
6
7
8
9
10
11
 long t0 = System.nanoTime();
  
  long count = values.stream().sorted().count();
  System.out.println(count);
  
  long t1 = System.nanoTime();
  
  long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
  System.out.println(String.format("sequential sort took: %d ms", millis));
  
  // sequential sort took: 899 ms

并行排序

1
2
3
4
5
6
7
8
9
10
11
 long t0 = System.nanoTime();
  
  long count = values.parallelStream().sorted().count();
  System.out.println(count);
  
  long t1 = System.nanoTime();
  
  long millis = TimeUnit.NANOSECONDS.toMillis(t1 - t0);
  System.out.println(String.format("parallel sort took: %d ms", millis));
  
  // parallel sort took: 472 ms

可以看到两段代码基本完全相同,但是并行排序并串行排序快了大约50%。差别仅是把stream()替换成了parallelStream()