2

什么是 Stream?

Stream(流)是一个来自数据源元素队列并支持聚合操作

元素队列 数据是以一系列元素的形式存在的,按照某种顺序排列,形成一个队列。在流的概念中,这些元素通常是连续到达的,可以逐个处理,而不必一次性加载整个数据集到内存中。
数据源 流的来源。 可以是集合,数组,I/O channel, 产生器generator 等。
聚合操作 对一系列元素执行计算以生成单个汇总值的过程。例如,计算流中所有元素的平均值、总和、最大值、最小值等。

一、Stream的特点

  1. 无存储:Stream本身不存储数据,它更像是一个高级迭代器,通过一系列流水线操作来处理数据。数据仍然存储在原始的数据源(如集合、数组等)中。
  2. 延迟执行:Stream中的操作是惰性执行的,只有在需要结果时(即触发终端操作时)才会实际执行。这允许通过多个中间操作组合复杂的查询,而无需立即计算中间结果。
  3. 不可变性:每次对Stream的操作都会返回一个新的Stream对象,而不会修改原始数据源。
  4. 并行处理能力:Stream支持并行流(parallelStream),允许在多核处理器上并行处理数据,提升处理性能。

Demo演示

不存储数据,不改变数据源

    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
        System.out.println(numbers); // 输出 [1, 2, 3, 4, 5]
        
        // 创建一个Stream
        Stream<Integer> numberStream = numbers.stream();
        
        // 看看numberStream的内容
        System.out.println(numberStream); // 输出 java.util.stream.ReferencePipeline$Head@2ecf5915

        // 原始数据源仍然存在且未改变
        System.out.println("Original numbers: " + numbers); // 输出 [1, 2, 3, 4, 5]

        // 使用Stream进行操作(例如打印)
        numberStream.forEach(System.out::println); // 1 2 3 4 5

        // 再次确认原始数据源未改变
        System.out.println("Original numbers after using stream: " + numbers); // 输出 [1, 2, 3, 4, 5]
    }

延时执行

    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        Stream<Integer> filteredStream = numbers.stream()
                .filter(n -> {
                    System.out.println("filter" + n); // 注意打印时间
                    return n > 2;
                });

        // 未执行终端操作执行完毕后
        System.out.println("Before forEach operation");

        // 触发终端操作
        filteredStream.forEach(System.out::println);

        // 终端操作执行完毕后
        System.out.println("After forEach operation");
    }

image.png

二、创建 Stream 流

顺序流和并行流

顺序流

  • 是一种单线程的流。
  • 它按照数据流的顺序依次处理每个元素,每个元素的处理都必须等待上一个元素的处理完成才能开始。
  • 使用顺序流可以保证数据处理的顺序和一致性。
  • 适用于处理数据量较小的情况。

并行流

  • 是一种多线程的流。
  • 它可以将数据分成多个部分并行处理,每个部分都可以在不同的线程中处理,从而提高处理效率。
  • 使用并行流可以提高数据处理的速度,适用于处理数据量较大、处理时间较长的情况。
  • 但是并行流也有一些缺点,比如线程之间的通信和同步会带来额外的开销,而且并行流可能会影响数据的顺序和一致性。

创建流

从集合创建流

List<String> list = Arrays.asList("a", "b", "c");  
Stream<String> stream = list.stream(); // 创建一个顺序流  
Stream<String> parallelStream = list.parallelStream(); // 创建一个并行流

从数组建流

int[] array = {1, 2, 3, 4, 5};  
IntStream intStream = Arrays.stream(array); // 创建一个IntStream顺序流  
IntStream intParallelStream = Arrays.stream(array).parallel(); // 创建一个IntStream并行流  

从值创建流

Stream<String> stream = Stream.of("a", "b", "c"); // 创建一个顺序流 
        Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5).parallel();  // 创建一个并行流

......

note

顺序流:可以使用集合类的stream()方法、Arrays类的stream()方法、Stream类的of()方法、Stream类的iterate()方法、Stream类的generate()方法、Files类的lines()方法等来获取顺序流。

并行流:可以使用集合类的parallelStream()方法、Stream类的of()方法的parallel()方法、Stream类的iterate()方法的parallel()方法、Stream类的generate()方法的parallel()方法等来获取并行流。

三、Stream的操作类型

Stream的操作可以分为中间操作终端操作两类。

中间操作

返回一个新的Stream对象,允许多个操作链式调用。常见的中间操作有

  • filter(Predicate<? super T> predicate):根据给定的条件过滤出符合条件的元素。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        Stream<Integer> filteredStream = numbers.stream()
                .filter(n -> n % 2 == 0);
        System.out.println(filteredStream); // 输出 java.util.stream.ReferencePipeline$2@5b5a4aed
        filteredStream.forEach(System.out::println); // 输出 2 4 6
    }
  • map(Function<? super T, ? extends R> mapper):将每个元素通过给定的函数映射成另一个元素。
  • sorted():对Stream中的元素进行自然排序(针对实现了Comparable接口的元素)。
  • distinct():去除Stream中的重复元素(依赖元素的hashCode和equals方法)。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(5, 3, 8, 3, 1, 5, 9, 2, 6, 7);
        
        List<String> sortedDistinctMappedNumbers = numbers.stream()
                // 将每个整数映射为 numA 表示形式
                .map(num -> num.toString() + "A")
                // 去重
                .distinct()
                // 排序
                .sorted()
                // 收集结果到一个新的列表中
                .collect(Collectors.toList());

        // 打印结果
        System.out.println(sortedDistinctMappedNumbers); // 输出 [1A, 2A, 3A, 5A, 6A, 7A, 8A, 9A]
    }
  • limit(long maxSize):限制Stream中元素的数量。
  • skip(long n):跳过Stream中的前n个元素。
  • peek(Consumer<? super T> action):对流中的每个元素执行操作并返回一个新的流,主要用于调试。

终端操作

触发Stream的处理并生成结果。常见的终端操作有:

  • forEach(Consumer<? super T> action):对Stream中的每个元素执行给定的操作。
  • collect(Collector<? super T, A, R> collector):将Stream中的元素收集到一个新的集合中。
  • reduce(BinaryOperator<T> accumulator):对Stream中的元素进行归约操作,可以实现求和、求最大值、求最小值等操作。
    @Test
    void demo() {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

        int sum = numbers.stream()
                // 使用reduce方法进行求和
                .reduce(0, Integer::sum); // 初始值为0,累加器为Integer::sum
        System.out.println(sum); // 输出 15
    }
  • count():返回Stream中元素的数量。
  • min(Comparator<? super T> comparator):返回Stream中的最小元素。
  • max(Comparator<? super T> comparator):返回Stream中的最大元素。
  • anyMatch(Predicate<? super T> predicate):检查Stream中是否存在满足给定条件的元素。
  • allMatch(Predicate<? super T> predicate):检查Stream中的所有元素是否都满足给定条件。
  • noneMatch(Predicate<? super T> predicate):检查Stream中的所有元素是否都不满足给定条件。

三、Stream与迭代的对比

Stream迭代
惰性求值立即求值
可链式调用函数通常使用的是for或forEach
声明式的:告诉程序你想要什么结果命令式:告诉程序如何执行每一步操作

总结

中间操作: 返回的是一个Stream类型的对象,允许多个操作链式调用
终端操作:能访问数据源,不允许多个操作链式调用, 获取最终的结果数据。


吴季分
390 声望13 粉丝