一、和集合遍历比较
虽然 stream 和集合遍历很像,但是他们是有区别的,主要体现在 stream 更关注的是你要做什么,而集合遍历会细致到怎么做的问题,你需要在集合遍历里面给出具体的实现方式,这就导致了集合遍历不会有优化的可能,比如并行的操作元素,区别如下:
1. stream不会存储元素。
2. stream不会改变原来的集合。
3. stream会尽可能的懒加载,当你只需要前五个元素的时候,就不会对后面的元素进行处理。
二、Stream的使用
使用 stream 主要分三步:
1. 创建一个流。
2. 对流中的元素作一系列的操作。
3. 得出一个你需要的结果。
流的创建
1. 集合可以直接生成流对象。
List<String> list = Arrays.asList("张三","李四","王五");
Stream<String> stream = list.stream();
2. 数组可以使用 Arrays 类创建流。
String[] array = new String[]{"张三","李四","王五"};
Stream<String> stream = Arrays.stream(array);
// 可以指定范围
Stream<String> subStream = Arrays.stream(array, 0, 1);
3. 可以使用 Stream 类生成流,传入数组或者可变参数。
Stream<String> stream = Stream.of("张三", "李四", "王五");
// 生成一个空的流
Stream<String> emptyStream = Stream.empty();
// 生成一个无限的流
Stream<Double> infiniteStream = Stream.generate(Math::random);
// 生成一个序列的流,0 是种子
Stream<Integer> iterateStream = Stream.iterate(0, n -> n++);
4. 大量的 JAVA API 也可以创建流对象。
String contents = "张三,李四,王五";
Stream<String> patternStream = Pattern.compile("[\\P{L}]+").splitAsStream(contents);
try(Stream<String> fileStream = Files.lines(Paths.get("a.txt"))) {
}
5. 创建基本类型的流
IntStream intStream = IntStream.of(1, 2, 3, 4);
DoubleStream doubleStream = DoubleStream.of(1.0, 2.0, 3.0, 4.0);
// 创建 0 至 99 的流
IntStream zeroToNinetyNine = IntStream.range(0, 100);
// 创建 0 至 100 的流
IntStream zeroToHundred = IntStream.rangeClosed(0, 100);
其他的不一一列举...
操作元素
1. map 方法可以对元素进行操作,通常传入一个 lamda 表达式操作元素。返回的也是一个 stream ,可以实现像管道一样的操作。
Stream<String> stream = Stream.of("zhangsan", "lisi", "wangwu")
.map(String::toUpperCase);
// 如果元素又切出了小的 stream ,可以使用 flatMap 进行合成一个 stream
Stream<String> flatStream = Stream.of("zhang san", "li si", "wang wu")
.flatMap(e -> splitSubStream(e));
===============================================================================
private static Stream<String> splitSubStream(String string) {
return Arrays.stream(string.split(" "));
}
2. limit 方法可以对元素进行限制,比如下面的产生100个随机数。
Stream<Double> limitStream = Stream.generate(Math::random).limit(100);
3. skip 方法则可以跳过前面的 N 个元素。
Stream<String> skipStream = Stream.of("无用数据", "李四", "王五").skip(1);
4. concat 方法可以合并两个 stream。
Stream<String> concatStream = Stream.concat(Stream.of("张三", "李四"),
Stream.of( "王五"));
5. distinct 方法可用于去重。
Stream<String> distinctStream = Stream.of("张三", "张三", "王五").distinct();
6. sort 方法可用于排序。当然集合也有自己的排序方法,stream 的排序在用于只针对一部分内容进行排序的时候会非常有用。
String[] arr = new String[]{"noOrder","zhangsan","lisi","wangwu"};
Stream<String> orderStream = Arrays.stream(arr,1,3)
.sorted(Comparator.comparing(String::length));
7. peek 方法产生的是跟之前一摸一样的一个流,可以对每一个元素进行消费。
List<Double> list = Stream.iterate(1.0, p -> p * 2)
.peek(e -> System.out.println(e))
.limit(10)
.collect(Collectors.toList());
8. filter 方法可以过滤掉不想要的元素,传入一个 lamda 表达式,注入返回 true 表示保留这个元素,返回 false 表示过滤掉这个元素。
Stream<String> filterStream = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"));
得出程序需要的结果
1. 获取一个集合。
List<String> list = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"))
.collect(Collectors.toList());
Set<String> set = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"))
.collect(Collectors.toSet());
// 可以传入一个集合构造器,获取想要的集合类型
LinkedList<String> linkedList = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"))
.collect(Collectors.toCollection(LinkedList<String>::new));
String[] stringArray = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"))
.toArray(String[]::new);
2. 获取一个 Map,注意 key 值重复的问题。
Map<String, String> map = Stream.of("张三", "李四", "王五")
.collect(Collectors.toMap(e -> e.substring(0, 1), e -> e));
// 如果 key 值重复会报错,这个时候提供一个 lamda 处理旧值和新值
Map<String, String> map = Stream.of("张三","张四", "李四", "王五", "王六")
.collect(Collectors.toMap(e -> e.substring(0, 1),
e -> e ,(oldV,newV) -> oldV + newV));
// 生成其他类型的 Map
TreeMap<String, String> map = Stream.of("张三","张四", "李四", "王五", "王六")
.collect(Collectors.toMap(e -> e.substring(0, 1),
e -> e ,(oldV,newV) -> oldV + newV,
TreeMap<String,String>::new));
如果 key 值相同,我们想要把它们收集起来,可以使用下面的方法
Map<String, List<String>> listMap = Stream.of("张三","张四", "李四", "王五", "王六"
.collect(Collectors.groupingBy(e -> e.substring(0,1)));
Map<String, Set<String>> setMap = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1), Collectors.toSet()));
Map<String, Long> countMap = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1), Collectors.counting()));
// 以下是统计相关的方法
Map<String, Integer> totalMap = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1),
Collectors.summingInt(String::length)));
Map<String, Optional<String>> maxVal = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1),
Collectors.mapping(
e -> e, Collectors.maxBy(Comparator.naturalOrder()))));
Map<String, Optional<String>> minVal = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1),
Collectors.mapping(
e -> e, Collectors.minBy(Comparator.naturalOrder()))));
// 简单的收集元素
Map<String, Set<String>> setMap = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1),
Collectors.mapping(
e -> e, toSet())));
使用下面的方法对元素进行分类,只能按是和否来分
Map<Boolean, List<String>> map = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.partitioningBy(e -> "张".equals(e.substring(0, 1))));
System.out.println(map.get(true));
System.out.println(map.get(false));
3. 计算元素的个数。
long count = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> e.startsWith("王"))
.count();
4. 最大值最小值等统计信息
Optional<Integer> max = Stream.of(100, 200, 50, 999).max(Comparator.comparingInt(e -> e));
Optional<Integer> min = Stream.of(100, 200, 50, 999).min(Comparator.comparingInt(e -> e));
// 获取一个统计对象,可以得到很多统计上的信息
IntSummaryStatistics summary = Stream.of(100, 200, 50, 999).collect(Collectors.summarizingInt(e -> e));
summary.getMax();
summary.getMin();
summary.getSum();
summary.getAverage();
summary.getCount();
// 结合了 groupingBy 的统计
Map<String, IntSummaryStatistics> groupingSummary = Stream.of("张三", "张四", "李四", "王五", "王六")
.collect(Collectors.groupingBy(e -> e.substring(0, 1),
Collectors.summarizingInt(String::length)));
5. 查询元素
Optional<String> findFirst = Stream.of("张三", "李四", "王五")
.filter(e -> e.startsWith("王")).findFirst();
// 如果可以是任意的一个,则可以使用 parallel 并行加速
Optional<String> findAny = Stream.of("张三", "李四", "王五")
.parallel().filter(e -> e.startsWith("王")).findAny();
6. 匹配元素
boolean hasWang = Stream.of("张三", "李四", "王五")
.parallel().anyMatch(e -> e.startsWith("王"));
boolean isAllWang = Stream.of("张三", "李四", "王五")
.parallel().allMatch(e -> e.startsWith("王"));
boolean isNoneWang = Stream.of("张三", "李四", "王五")
.parallel().noneMatch(e -> e.startsWith("王"));
7. 合并成一个字符串,使用 “,” 分隔
String joining = Stream.of("张三", "李四", "王五", "王六")
.filter(e -> !e.startsWith("王"))
.collect(Collectors.joining(","));
8. 对流中的元素进行计算操作
使用 reduce 方法的时候注意它是并行的,这意味着不要用它操作非线程安全的容器,可以使用 collect 方法代替。
Optional<Integer> sum = Stream.of(100, 200, 50, 999).reduce((x, y) -> x + y);
// 在前面加一个值 0,可以让流是空的情况下返回该值,注意 0 会参与到运算中
Integer identitySum = Stream.of(100, 200, 50, 999).reduce(0, Integer::sum);
// 当需要操作元素之后再进行合并,可以使用下面这种累加器的形式,最后一行是因为并行运行的时候可以把已经累计的结果加起来
Integer parallelSum = Stream.of("张三", "李四", "王五", "王六").reduce(0,
(total, word) -> total + word.length(),
(total1, total2) -> total1 + total2);
// 使用 collect 代替 reduce
// 第一个参数是构造器 第二个参数是用于添加元素的累加器 第三个参数是用于合并结果的合并器
BitSet opBitSet = Arrays.asList(1, 2, 3, 4).stream()
.collect(BitSet::new, BitSet::set, BitSet::or);
// 相较于上面的那种方法,这种方式更加简单有效
int easierSum = Stream.of("张三", "李四", "王五", "王六").mapToInt(String::length).sum();
三、并行流
使用并行流的前提是操作元素是可以并行的,绝对不要像下面那样使用
// countArray 不是一个线程安全的容器,流中也不允许使用线程安全的容器
int[] countArray = new int[5];
Stream.of("zhangsan", "lisi", "wangwu").parallel().forEach(e -> {
if (e.length() < 5) {
countArray[e.length()]++;
}
});
可以使用下面这种形式完成同样的功能
Stream.of("zhangsan", "lisi", "wangwu").parallel()
.filter(e -> e.length() < 5)
.collect(groupingBy(e -> e.length(), counting()));
有时候可以声明不要求元素有顺序来对流进行加速,比如下面的例子
Stream.of("zhangsan", "lisi", "wangwu","zhangsan").parallel()
.unordered()
.distinct()
.collect(toList());
Stream.of("zhangsan", "lisi", "wangwu","zhangsan").parallel()
.unordered()
.limit(3);
不要把所有的流都转换成并行流,只有需要在内存中大量计算的情况下才这样做,如果规模不大,那么把它变成并行流的所支付的成本是不合算的。