Stream的使用

本文详细介绍了JavaStreamAPI的使用,包括stream与集合遍历的不同、流的创建、操作元素(如map,limit,skip等)、并行流的使用以及如何避免不合理地转换为并行流。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、和集合遍历比较

虽然 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);

不要把所有的流都转换成并行流,只有需要在内存中大量计算的情况下才这样做,如果规模不大,那么把它变成并行流的所支付的成本是不合算的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值