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()。