Stream
java.util.stream
包是Java 8引入的全新的流式API,与java.io
的InputStream
和OutputStream
不同,Stream
代表的是任意Java对象的序列,两者对比如下:
java.io | java.util.stream | |
---|---|---|
存储 | 顺序读写的byte 或char | 顺序输出的任意Java对象实例 |
用途 | 序列化至文件或网络 | 内存计算/业务逻辑 |
一个顺序输出的Java对象序列,不就是一个List
容器吗?这个Stream
和List
也不一样,List
存储的每个元素都是已经存储在内存中的某个Java对象,而Stream
输出的元素可能并没有预先存储在内存中,而是实时计算出来的。换句话说,List
的用途是操作一组已存在的Java对象,而Stream
实现的是惰性计算,两者对比如下:
java.util.List | java.util.stream | |
---|---|---|
元素 | 已分配并存储在内存 | 可能未分配,实时计算 |
用途 | 操作一组已存在的Java对象 | 惰性计算 |
Stream
可用于无限序列,但注意每个Stream
都只是存储转换规则,只有调用求值方法时才会真正地进行结果的计算(此处要计算无限序列,必须先用limit(maxSize)
限定为有限序列,否则将一直循环计算下去),例如:
public static void main (String[] args) {
Stream<BigInteger> naturals = createNaturalStream(); // 不计算
Stream<BigInteger> s2 = naturals.map(n -> n.multiply(n)); // 不计算
Stream<BigInteger> s3 = s2.limit(100); // 不计算
s3.forEach(System.out::println); // 计算
// 改写成链式操作:
createNaturalStream() // 不计算
.map(n -> n.multiply(n)) // 不计算
.limit(100) // 不计算
.forEach(System.out.println); // 计算
}
public static Stream<BigInteger> createNaturalStream() {
return Stream.generate(new Supplier<>() {
BigInteger bigInteger = BigInteger.valueOf(0);
@Override
public BigInteger get () {
bigInteger = bigInteger.add(BigInteger.valueOf(1));
return bigInteger;
}
});
}
创建Stream
-
Stream.of()
:Stream<String> stream = Stream.of("A", "B", "C", "D");
-
基于数组:
String[] arr = {"A", "B", "C"}; Stream<String> stream = Arrays.stream(arr);
-
基于
Collection
:List<String> list = List.of("X", "Y", "Z"); Stream<String> stream = list.stream();
-
基于
Supplier
:Supplier
接口用于提供无限序列的实现规则Supplier<String> sp = new Supplier<>() { private String s = ""; @Override public String get () { s += "*"; return s; } }; Stream<String> s = Stream.generate(sp);
-
一些API的接口:
-
Files
类的lines()
方法可以把一个文件变成一个Stream
,每个元素代表文件的一行内容(此方法对于按行遍历文本文件十分有用):try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) { ... }
-
正则表达式的
Pattern
对象有一个splitAsStream()
方法,可以直接把一个长字符串分割成Stream
序列而不是数组:Pattern p = Pattern.compile("\\s+"); Stream<String> s = p.splitAsStream("The quick brown fox jumps over the lazy dog"); s.forEach(System.out::println);
-
基本类型Stream
因为Java的范型不支持基本类型,所以我们无法用Stream<int>
这样的类型,会发生编译错误。为了保存int
,只能使用Stream<Integer>
,但这样会产生频繁的装箱、拆箱操作。为了提高效率,Java标准库提供了IntStream
、LongStream
和DoubleStream
这三种使用基本类型的Stream
,它们的使用方法和范型Stream
没有大的区别,设计这三个Stream
的目的是提高运行效率:
// 将int[]数组变为IntStream:
IntStream is = Arrays.stream(new int[] { 1, 2, 3 });
// 将Stream<String>转换为LongStream:
LongStream ls = List.of("1", "2", "3").stream().mapToLong(Long::parseLong);
通过基本类型Stream
,可以实现集合、包装类型数组与基本类型数组之间的转换:
// Integer[] <---> Stream<Integer>
Integer[] arr = Stream.of(1, 2, 3).toArray(Integer[]::new);
Stream<Integer> stream = Arrays.stream(arr);
// List<Integer> <---> Stream<Integer>
List<Integer> list = Stream.of(1, 2, 3).collect(Collectors.toList());
Stream<Integer> stream = list.stream();
// Stream<Integer> <---> IntStream
Stream<Integer> stream = IntStream.of(1, 2, 3).boxed();
IntStream intStream = stream.mapToInt(Integer::intValue);
// IntStream <---> int[]
IntStream intStream = Arrays.stream(new int[] {1, 2, 3});
int[] arr = intStream.toArray();
转换操作
Stream
的转换操作主要有:
-
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
:用于将当前Stream
转换为另一个Stream
- 有返回基本类型
Stream
的map
方法:IntStream mapToInt(ToIntFunction<? super T> mapper)
LongStream mapToLong(ToLongFunction<? super T> mapper)
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)
Stream.of(" 2019-12-31 ", "2020 - 01-09 ", "2020- 05 - 01 ") .map(date -> date.replaceAll("\\s", "")) .forEach(System.out::println);
- 有返回基本类型
-
Stream<T> filter(Predicate<? super T> predicate)
:用于从当前Stream
中过滤得到另一个Stream
IntStream.of(1, 2, 3, 4, 5, 6, 7, 8, 9) .filter(n -> n % 2 != 0) .forEach(System.out::println);
-
Stream<T> sorted()
:排序,要求所有Stream
元素都已经实现Comparable
接口Stream<T> sorted(Comparator<? super T> comparator)
:重载方法,传入比较器// String已实现Comparable: Stream.of("Orange", "apple", "Banana") .sorted() .forEach(System.out::println); // 指定比较规则: Stream.of("Orange", "apple", "Banana") .sorted(String::compareToIgnoreCase) .forEach(System.out::println);
-
Stream<T> distinct()
:去重,Stream
元素的equals()
方法决定Stream.of("apple", "apple", "pear") .distinct() .forEach(System.out::println);
截取操作
Stream<T> skip(long n)
:跳过n
个元素Stream<T> limit(long maxSize)
:截取最多前maxSize
个元素
合并操作
-
static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b)
:静态方法,用于将两个Stream
合并成一个Stream
Stream<String> s = Stream.concat( Stream.of("A", "B", "C"), Stream.of("D", "E") );
-
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
:先将当前Stream
的每个元素都map
成Stream
类型,再将这些Stream
都合并成一个总的Stream
后返回// 将多个List合并成一个Stream Stream<List<Integer>> s = Stream.of( Arrays.asList(1, 2, 3), Arrays.asList(4, 5, 6), Arrays.asList(7, 8, 9) ); Stream<Integer> i = s.flatMap(list -> list.stream());
并行处理
通常情况下,对Stream
的元素进行处理是单线程的,即一个一个元素进行处理。但是很多时候,我们希望可以并行处理Stream
的元素,因为在元素数量非常大的情况,并行处理可以大大加快处理速度。
把一个普通Stream
转换为可以并行处理的Stream
非常简单,只需要用parallel()
进行转换:
Stream<String> s = Stream.of("apple", "pear", "orange");
String[] result = s.parallel() // 变成一个可以并行处理的Stream
.sorted() // 可以进行并行排序
.toArray(String[]::new);
经过parallel()
转换后的Stream
只要可能,就会对后续操作进行并行处理。我们不需要编写任何多线程代码就可以享受到并行处理带来的执行效率的提升。
聚合操作
以上操作都只是存储转换规则,并不会进行计算;只有聚合操作才会进行真正的计算,且每个Stream
仅能执行一次,这是因为每个Stream
在执行完聚合操作后,流已经关闭了
-
Optional<T> reduce(BinaryOperator<T> accumulator)
:用于将当前Stream
的所有元素按照聚合函数聚合成一个结果,它以第一个Stream
元素作为第一个结果,accumulator
执行次数等于元素数-1
T reduce(T identity, BinaryOperator<T> accumulator)
:重载方法,它以identity
作为第一个结果,accumulator
执行次数等于元素数
// 下面以IntStream为例,方法定义略有不同,但使用完全一致 // 1. 第一个元素为初始值 OptionalInt op = IntStream.of(1, 2, 3).reduce((res, value) -> res + value); if (op.isPresent()) { System.out.println(op.getAsInt()); } // 等价于: IntStream.of(1, 2, 3).reduce(Integer::sum).ifPresent(System.out::println); // 2. 指定初始值 int sum = IntStream.of(1, 2, 3).reduce(0, Integer::sum); System.out.println(sum);
-
long count()
:返回元素个数Optional<T> max(Comparator<? super T> comparator)
:找出最大元素Optional<T> min(Comparator<? super T> comparator)
:找出最小元素 -
<R, A> R collect(Collector<? super T, A, R> collector)
:按指定规则将Stream
输出为一个容器// 1. 输出为List: List<String> list = Stream.of("Apple", "Pear", "Orange").collect(Collectors.toList()); // 等价于: List<String> list = Stream.of("Apple", "Pear", "Orange").toList(); // 2. 输出为Set: Set<String> list = Stream.of("Apple", "Apple", "Pear").collect(Collectors.toSet()); // 3. 输出为Map: Map<String, String> map = Stream.of("APPL:Apple", "MSFT:Microsoft") .collect(Collectors.toMap( s -> s.substring(0, s.indexOf(':')), s -> s.substring(s.indexOf(':') + 1) )); // 4. 输出为分组Map(组表示为key,元素列表为value): Map<String, List<String>> groups = Stream .of("Apple", "Banana", "Blackberry", "Coconut", "Avocado", "Cherry", "Apricots") .collect(Collectors.groupingBy( s -> s.substring(0, 1), Collectors.toList() // 默认值,可省略 ));
-
Object[] toArray()
:输出为数组<A> A[] toArray(IntFunction<A[]> generator)
:重载方法String[] arr = Stream.of("Apple", "Pear", "Orange").toArray(String[]::new);
-
boolean allMatch(Predicate<? super T> predicate)
:测试是否所有元素均满足测试条件boolean anyMatch(Predicate<? super T> predicate)
:测试是否至少有一个元素满足测试条件 -
void forEach(Consumer<? super T> action)
:遍历输出
针对
IntStream
、LongStream
和DoubleStream
,还额外提供了以下聚合方法:
OptionalDouble average()
:对所有元素求平均数int sum()
、long sum()
、double sum()
:对所有元素求和