Java Stream性能优化技巧 | 优雅处理集合数据

发布时间:2024年01月02日

一、简介

??Stream是Java 8中新增的一个API,它提供了一种流式处理集合数据的方法。Stream可以看作是一种高级的迭代器,它不直接存储数据,而是通过对数据的一系列操作来实现对数据的处理。Stream操作可以串行执行,也可以并行执行,充分发挥多核处理器的性能。

1.1 与传统集合的对比

??传统的集合操作需要使用迭代器或循环来遍历集合元素,并进行相应的操作。Stream可以通过一系列的操作链式调用,在一行代码中完成复杂的数据处理。这种方式更加简洁、易读,并且能够提高代码的可维护性和可读性。

  • 链式编程风格:可以使用Lambda表达式来描述数据处理逻辑,代码更加简洁、易读。
  • 延迟执行:操作是延迟执行的,只有在终端操作时才会触发处理,这样可以避免不必要的中间操作,提高了性能和效率。
  • 并行处理:可以方便地进行并行处理,充分利用多核处理器的性能,加快数据处理速度。

1.2 什么是延迟执行

??延迟执行是一个很重要的特征,需要用一个例子来说明清楚。
??假设我们有一个整数列表,我们希望对其中的偶数进行平方操作,并计算平方后的偶数的总和。使用延迟执行的特性,我们可以将操作链连接起来,只在需要结果时才执行。

import java.util.Arrays;
import java.util.List;

public class DelayedExecutionDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        int sum = numbers.stream()
                        .filter(n -> n % 2 == 0) // 筛选偶数
                        .map(n -> n * n) // 平方操作
                        .reduce(0, Integer::sum); // 求和

        System.out.println("Sum: " + sum);
    }
}

??在上述代码中,首先创建了一个包含整数的列表numbers。然后,我们使用Stream对列表进行操作。首先,我们使用filter()方法筛选出偶数,然后使用map()方法对每个偶数进行平方操作,最后使用reduce()方法求和。

??这里的关键是,这些操作并不会立即执行。它们只是形成了一个操作链。直到我们调用终端操作reduce()时,才会触发实际的计算。如果我们将代码中的reduce()操作移除,那么最终的结果将不会被计算和打印出来。这是因为没有终端操作来触发执行。

??延迟执行的好处是,我们可以在构建操作链时进行灵活的操作组合,只执行我们真正需要的操作,避免了对整个数据集进行不必要的处理。这样可以提高性能,并且使得代码更加清晰、易于理解和维护。






二、基本操作

2.1 筛选与过滤

2.1.1 filter:根据条件筛选元素

??根据指定的条件筛选流中的元素。它接受一个Predicate函数作为参数,用于判断每个元素是否满足条件,只有满足条件的元素才会被保留在流中。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

List<Integer> evenNumbers = numbers.stream()
                                   .filter(n -> n % 2 == 0) // 筛选偶数
                                   .collect(Collectors.toList());

System.out.println(evenNumbers); // 输出: [2, 4, 6, 8, 10]
2.1.2 distinct:去除重复元素

??去除流中的重复元素,保留唯一的元素。

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 4, 4, 5);

List<Integer> distinctNumbers = numbers.stream()
                                       .distinct() // 去除重复元素
                                       .collect(Collectors.toList());

System.out.println(distinctNumbers); // 输出: [1, 2, 3, 4, 5]



2.2 映射与转换

2.2.1 map:对元素进行映射转换

??对流中的元素进行映射转换。它接受一个Function函数作为参数,用于将每个元素映射为另一个值。
??通过map方法,我们可以将原始的字符串列表转换为一个包含字符串长度的整数列表。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<Integer> nameLengths = names.stream()
                                 .map(String::length) // 映射为名字长度
                                 .collect(Collectors.toList());

System.out.println(nameLengths); // 输出: [5, 3, 7]
2.2.2 flatMap:扁平化流,展开嵌套结构

??将嵌套的流扁平化,展开嵌套结构。它将流中的每个元素转换为一个新的流,然后将这些新的流合并成一个单一的流。
??以下代码将原始的嵌套列表转换为一个不带嵌套的整数列表。

List<List<Integer>> nestedNumbers = Arrays.asList(
    Arrays.asList(1, 2, 3),
    Arrays.asList(4, 5, 6),
    Arrays.asList(7, 8, 9)
);

List<Integer> flattenedNumbers = nestedNumbers.stream()
                                              .flatMap(List::stream) // 扁平化流
                                              .collect(Collectors.toList());

System.out.println(flattenedNumbers); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]



2.3 排序与比较

2.3.1 sorted:对流中元素进行排序

??对流中的元素进行排序。它可以接受一个Comparator函数作为参数,用于指定排序的规则。

List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 9, 1, 4, 7, 6);

List<Integer> sortedNumbers = numbers.stream()
                                     .sorted() // 默认升序排序
                                     .collect(Collectors.toList());

System.out.println(sortedNumbers); // 输出: [1, 2, 3, 4, 5, 6, 7, 8, 9]

??以上代码演示了如何对整数列表进行升序排序,获得一个按照默认排序规则(自然顺序)排列的新列表。

??如果需要按照自定义的排序规则进行排序,可以传入一个Comparator函数作为参数。例如,如果要按照数字的逆序进行排序,可以使用如下代码:

List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 9, 1, 4, 7, 6);

List<Integer> reverseSortedNumbers = numbers.stream()
                                            .sorted(Comparator.reverseOrder()) // 逆序排序
                                            .collect(Collectors.toList());

System.out.println(reverseSortedNumbers); // 输出: [9, 8, 7, 6, 5, 4, 3, 2, 1]

2.3.2 max和min:获取最大值和最小值

??分别获取流中的最大值和最小值。它们可以接受一个Comparator函数作为参数,用于指定比较的规则。

List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 9, 1, 4, 7, 6);

Optional<Integer> maxNumber = numbers.stream()
                                     .max(Integer::compareTo); // 获取最大值

Optional<Integer> minNumber = numbers.stream()
                                     .min(Integer::compareTo); // 获取最小值

System.out.println("最大值: " + maxNumber.orElse(null)); // 输出: 最大值: 9
System.out.println("最小值: " + minNumber.orElse(null)); // 输出: 最小值: 1

??以上代码通过使用Optional类来处理可能不存在最大值或最小值的情况,可以避免空指针异常。



2.4 统计与归约

2.4.1 count:计算流中元素的个数

??计算流中元素的个数。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

long count = names.stream()
                 .count();

System.out.println("元素个数: " + count); // 输出: 元素个数: 3

2.4.2 reduce:对流中元素进行归约操作

??对流中的元素进行归约操作。它接受一个BinaryOperator函数作为参数,用于定义归约的规则。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
                 .reduce(0, Integer::sum); // 计算元素的和

System.out.println("元素的和: " + sum); // 输出: 元素的和: 15

??除了求和之外,reduce方法还可以用于执行其他类型的归约操作,例如找到最大值、最小值,连接字符串等:

List<Integer> numbers = Arrays.asList(5, 3, 8, 2, 9, 1, 4, 7, 6);

Optional<Integer> max = numbers.stream()
                               .reduce(Integer::max); // 找到最大值

System.out.println("最大值: " + max.orElse(null)); // 输出: 最大值: 9






三、Stream的中间操作

3.1 limit和skip:限制和跳过流中的元素

??限制流中元素的数量,而使用skip操作可以跳过流中的前几个元素。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> limitedNumbers = numbers.stream()
                                      .limit(3) // 限制前3个元素
                                      .collect(Collectors.toList());

System.out.println("限制后的元素: " + limitedNumbers); // 输出: 限制后的元素: [1, 2, 3]

List<Integer> skippedNumbers = numbers.stream()
                                      .skip(2) // 跳过前2个元素
                                      .collect(Collectors.toList());

System.out.println("跳过后的元素: " + skippedNumbers); // 输出: 跳过后的元素: [3, 4, 5]

??调用limit方法并传入一个限制值,可以获取流中指定数量的元素。而调用skip方法并传入一个跳过值,可以跳过流中的前几个元素。


3.2 peek:查看流中元素,调试和日志输出

??查看流中的元素,对其进行调试和日志输出等操作。它接受一个Consumer函数作为参数,用于对每个元素进行处理。通过传入一个处理函数,可以在流的每个元素上执行自定义的操作,例如输出日志、调试等。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<String> processedNames = names.stream()
                                   .peek(System.out::println) // 输出每个元素
                                   .map(String::toUpperCase) // 转换为大写
                                   .collect(Collectors.toList());

System.out.println("处理后的元素: " + processedNames); // 输出: 处理后的元素: [ALICE, BOB, CHARLIE]

3.3 parallel和sequential:并行和串行操作流

??将流转换为并行流,从而允许并行处理流中的元素。而使用sequential操作可以将并行流转换为串行流。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

List<Integer> parallelResult = numbers.stream()
                                      .parallel() // 转换为并行流
                                      .map(number -> number * 2)
                                      .collect(Collectors.toList());

System.out.println("并行处理结果: " + parallelResult); // 输出: 并行处理结果: [2, 4, 6, 8, 10]

List<Integer> sequentialResult = parallelResult.stream()
                                               .sequential() // 转换为串行流
                                               .map(number -> number + 1)
                                               .collect(Collectors.toList());

System.out.println("串行处理结果: " + sequentialResult); // 输出: 串行处理结果: [3, 5, 7, 9, 11]

??调用parallel方法,可以将流转换为并行流,从而允许并行处理流中的元素。而调用sequential方法可以将并行流转换为串行流,以便按顺序处理流中的元素。


3.4 unordered:取消有序性,提升性能

??取消流的有序性,从而提升处理性能。这在某些情况下特别有用,例如在进行聚合操作时,取消有序性可以提升处理性能。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
                 .unordered() // 取消有序性
                 .mapToInt(Integer::intValue)
                 .sum();

System.out.println("元素的和: " + sum); // 输出: 元素的和: 15






四、Stream的终端操作

4.1 forEach:遍历流中的元素

??对流中的每个元素进行遍历并执行指定的操作。

// 对名字列表中的每个名字打印了一条问候语
List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

names.stream()
     .forEach(name -> System.out.println("Hello, " + name));

// 输出:
// Hello, Alice
// Hello, Bob
// Hello, Charlie

4.2 collect:将流转换为其他数据结构

??将流转换为其他数据结构,例如列表、集合、映射等。通过在collect方法中传入Collectors.toList(),我们可以将流中满足条件的元素收集到一个新的列表中。

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");

List<String> collectedNames = names.stream()
                                  .filter(name -> name.length() > 4)
                                  .collect(Collectors.toList());

System.out.println("收集到的名字: " + collectedNames); // 输出: 收集到的名字: [Alice, Charlie]

4.3 findFirst和findAny:查找符合条件的第一个元素

??查找流中符合条件的第一个元素。

// 分别查找列表中的第一个偶数和任意一个奇数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

Optional<Integer> firstEvenNumber = numbers.stream()
                                           .filter(number -> number % 2 == 0)
                                           .findFirst();

System.out.println("第一个偶数: " + firstEvenNumber.orElse(-1)); // 输出: 第一个偶数: 2

Optional<Integer> anyOddNumber = numbers.stream()
                                        .filter(number -> number % 2 != 0)
                                        .findAny();

System.out.println("任意一个奇数: " + anyOddNumber.orElse(-1)); // 输出: 任意一个奇数: 1

4.4 anyMatch、allMatch和noneMatch:判断流中元素是否满足条件

??判断流中的元素是否满足指定的条件。

// 判断列表中是否存在偶数、是否全部为偶数以及是否没有负数
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

boolean hasEvenNumber = numbers.stream()
                               .anyMatch(number -> number % 2 == 0);

System.out.println("是否存在偶数: " + hasEvenNumber); // 输出: 是否存在偶数: true

boolean allEvenNumbers = numbers.stream()
                               .allMatch(number -> number % 2 == 0);

System.out.println("是否全部为偶数: " + allEvenNumbers); // 输出: 是否全部为偶数: false

boolean noNegativeNumbers = numbers.stream()
                                  .noneMatch(number -> number < 0);

System.out.println("是否没有负数: " + noNegativeNumbers); // 输出: 是否没有负数: true

4.5 reduce:对流中元素进行归约操作

??对流中的元素进行归约操作,例如求和、求最大值、求字符串连接等。

// 求列表中元素的和、最大值,并将所有元素连接成一个字符串。

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);

int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);

System.out.println("元素的和: " + sum); // 输出: 元素的和: 15

Optional<Integer> max = numbers.stream()
                               .reduce(Integer::max);

System.out.println("最大值: " + max.orElse(-1)); // 输出: 最大值: 5

String concatenatedString = numbers.stream()
                                  .map(String::valueOf)
                                  .reduce("", (a, b) -> a + b);

System.out.println("连接后的字符串: " + concatenatedString); // 输出: 连接后的字符串: 12345






五、Stream的并行操作

??调用parallel()方法将顺序流转换为并行流,在使用并行流进行求和操作时,由于并行处理的特性,执行顺序是不确定的,每次运行可能得到不同的结果。

代码示例:

import java.util.Arrays;
import java.util.List;

public class ParallelStreamDemo {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);

        int sum = numbers.parallelStream()
                        .reduce(0, Integer::sum);

        System.out.println("Sum: " + sum);
    }
}

??并非所有的操作都适合并行处理,在某些情况下,使用并行流可能会导致性能下降,甚至产生错误的结果。因此谨慎使用,选择合适的场景进行并行处理。

文章来源:https://blog.csdn.net/CharmaineXia/article/details/135332751
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。