探索 Stream 流的强大功能和算法

发布时间 2023-06-19 13:50:25作者: ZnPi

Java 8 引入了 Stream API,为处理集合数据提供了一种更便捷、高效的方式。Stream 流提供了一套丰富的 API,可以让开发者更简洁、优雅地处理数据。本文将介绍 Java Stream 流的基本概念、核心特性和常见用法,帮助您更好地理解和应用 Stream 流。

简介

Stream 是 Java 8 引入的一个概念,它代表了一系列元素的序列,支持各种操作来处理和转换这些元素。Stream 提供了一种流式处理的方式,可以通过链式调用操作方法来处理数据,而不需要显式地进行迭代或循环。

创建 Stream 流

Java 中的集合类可以通过 stream() 方法或 parallelStream() 方法来获取对应的 Stream 流。除此之外,还可以使用 Stream.of() 方法创建包含指定元素的流,或使用 Arrays.stream() 方法将数组转换为流。下面是一些例子:

  1. 从集合创建流: 假设我们有一个存储整数的 List 集合,我们可以使用 stream() 方法从该集合创建一个流:

    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    Stream<Integer> stream = numbers.stream();
    
  2. 从数组创建流: 如果我们有一个整数数组,可以使用 Arrays 类的 stream() 方法来创建一个流:

    int[] array = {1, 2, 3, 4, 5};
    IntStream stream = Arrays.stream(array);
    
  3. 使用 Stream.of() 方法创建流。 Stream 类中的 of() 方法允许我们直接传入多个元素来创建一个流:

    Stream<String> stream = Stream.of("apple", "banana", "orange");
    
  4. 使用 BufferedReader 从文件创建流: 我们可以使用 Java 的 IO 类来从文件中逐行读取内容并创建一个流:

    try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"))) {
        Stream<String> stream = reader.lines();
        // 处理流中的内容
    } catch (IOException e) {
        e.printStackTrace();
    }
    

Stream 流的操作方法

Stream 流提供了丰富的 API,可以分为两大类:中间操作和终端操作。中间操作可以用于转换和过滤流中的元素,而终端操作则用于产生最终结果或副作用。

  • 中间操作:常见的中间操作包括 filtermapflatMapdistinctsorted 等。filter 方法用于根据条件筛选元素,map 方法用于对元素进行映射操作,flatMap 方法用于扁平化处理嵌套结构的流,distinct 方法用于去重,sorted 方法用于排序等。
  • 终端操作:常见的终端操作包括 forEachcollectreducecountminmax 等。forEach 方法用于对流中的每个元素执行特定操作,collect 方法用于将流转换为其他数据结构,reduce 方法用于将流的元素合并为单个结果,count 方法用于计算流中的元素数量,minmax 方法用于获取最小值和最大值等

filter

filter 操作用于根据给定的条件筛选流中的元素。假设我们有一个包含整数的 List 集合,我们想筛选出所有大于等于 10 的元素。我们可以使用 filter 操作来实现这个目标:

List<Integer> numbers = Arrays.asList(5, 10, 15, 20, 25, 30);
Stream<Integer> filteredStream = numbers.stream()
                                        .filter(num -> num >= 10);
filteredStream.forEach(System.out::println);

在上述示例中,我们首先调用 numbers 集合的 stream() 方法来获取一个流。然后,我们使用 filter 操作,并提供一个 Lambda 表达式作为参数,该 Lambda 表达式指定了筛选条件(num >= 10)。最后,我们调用 forEach 终端操作来打印筛选后的结果。

输出结果将是:

10
15
20
25
30

通过使用filter操作,我们成功筛选出了所有大于等于10的元素,并打印出结果。

map

map 操作用于对流中的每个元素执行某种操作,并将操作的结果映射到一个新的流中。下面是一个使用 map 的例子:

假设我们有一个存储员工对象的 List 集合,每个员工对象包含姓名和年龄属性。我们想要将员工的姓名提取出来,并创建一个新的流来存储这些姓名。我们可以使用 map 操作来实现这个目标:

List<Employee> employees = Arrays.asList(
        new Employee("Alice", 25),
        new Employee("Bob", 30),
        new Employee("Charlie", 35)
);

Stream<String> nameStream = employees.stream()
                                      .map(Employee::getName);
nameStream.forEach(System.out::println);

在上述示例中,我们首先创建了一个包含员工对象的 List 集合。然后,我们使用stream()方法获取该集合的流。接下来,我们使用 map 操作,并传递一个方法引用(Employee::getName),该方法引用表示对每个员工对象调用 getName 方法来获取姓名。最后,我们使用 forEach 终端操作来打印映射后的结果。

输出结果将是:

Alice
Bob
Charlie

通过使用 map 操作,我们成功将员工对象的姓名提取出来,并创建了一个新的流来存储这些姓名。

flatMap

flatMap 操作用于处理嵌套结构的流,并将其扁平化为单层流。下面是一个使用 flatMap 的例子:

假设我们有一个存储多个单词的 List 集合,每个单词又以空格分隔开。我们想要将这些单词拆分并创建一个包含所有单词的单层流。我们可以使用 flatMap 操作来实现这个目标:

List<String> words = Arrays.asList("Hello World", "Java Stream", "FlatMap Example");

Stream<String> flatMapStream = words.stream()
                                    .flatMap(line -> Arrays.stream(line.split(" ")));
flatMapStream.forEach(System.out::println);

在上述示例中,我们首先创建了一个包含多个单词的 List 集合。然后,我们使用 stream() 方法获取该集合的流。接下来,我们使用 flatMap 操作,并传递一个 Lambda 表达式(line -> Arrays.stream(line.split(" "))),该 Lambda 表达式表示将每行字符串拆分成单词,并创建一个单层流。最后,我们使用forEach 终端操作来打印拆分后的结果。

输出结果将是:

Hello
World
Java
Stream
FlatMap
Example

通过使用 flatMap 操作,我们成功将嵌套的流结构扁平化为一个单层流,并打印出拆分后的所有单词。

使用 flatMap 操作可以方便地处理嵌套的流结构,将其转换为单层流以便进行后续操作。这在处理嵌套集合、嵌套对象或多层数据结构时非常有用。

distinct

distinct 操作用于去除流中的重复元素,只保留不重复的元素。下面是一个使用 distinct 的例子:

假设我们有一个存储整数的 List 集合,其中包含一些重复的元素。我们想要创建一个新的流,只包含不重复的元素。我们可以使用 distinct 操作来实现这个目标:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 2, 3, 5, 1, 6);
Stream<Integer> distinctStream = numbers.stream().distinct();
distinctStream.forEach(System.out::println);

在上述示例中,我们首先创建了一个包含整数的 List 集合,其中包含一些重复的元素。然后,我们使用 stream() 方法获取该集合的流。接下来,我们使用distinct 操作来去除重复元素。最后,我们使用 forEach 终端操作来打印去重后的结果。

输出结果将是:

1
2
3
4
5
6

通过使用 distinct 操作,我们成功去除了流中的重复元素,并打印出不重复的结果。

使用 distinct 操作可以方便地去除流中的重复元素,确保流中的元素是唯一的。这在处理需要排除重复数据的场景下非常有用,例如去重操作或统计不重复元素的数量。

sorted

sorted 操作用于对流中的元素进行排序。下面是一个使用 sorted 的例子:

假设我们有一个存储整数的List集合,我们想要按照从小到大的顺序对这些整数进行排序。我们可以使用sorted操作来实现这个目标:

List<Integer> numbers = Arrays.asList(5, 2, 8, 1, 3);
Stream<Integer> sortedStream = numbers.stream().sorted();
sortedStream.forEach(System.out::println);

在上述示例中,我们首先创建了一个包含整数的List集合。然后,我们使用 stream() 方法获取该集合的流。接下来,我们使用 sorted 操作对流中的元素进行排序,默认是按照自然顺序进行排序。最后,我们使用 forEach 终端操作来打印排序后的结果。

输出结果将是:

1
2
3
5
8

通过使用sorted操作,我们成功对流中的整数进行了排序,并打印出排序后的结果。

除了默认的自然排序外,我们还可以使用sorted操作的重载版本,传入一个Comparator来指定自定义的排序方式。这样可以根据具体的需求对流中的元素进行灵活的排序。

使用sorted操作可以方便地对流中的元素进行排序,无论是按照自然顺序还是自定义顺序。排序操作在许多场景中都是必需的,例如对数据进行升序或降序排列,或者根据特定的字段进行排序。

forEach

forEach 操作用于对流中的每个元素执行指定的操作。下面是一个使用 forEach 的例子:

假设我们有一个存储字符串的 List 集合,我们想要遍历并打印出每个字符串。我们可以使用 forEach 操作来实现这个目标:

List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
names.stream().forEach(System.out::println);

在上述示例中,我们首先创建了一个包含字符串的List集合。然后,我们使用 stream() 方法获取该集合的流。接下来,我们使用 forEach 操作,并传递一个方法引用(System.out::println),该方法引用表示对每个元素调用 println 方法来打印字符串。最后,我们的操作是通过终端操作 forEach 来实际执行的。

输出结果将是:

Alice
Bob
Charlie

通过使用forEach操作,我们成功遍历了流中的每个元素并将其打印出来。

使用forEach操作可以对流中的每个元素执行指定的操作,例如打印、更新或其他自定义操作。它提供了一种简洁的方式来对流中的元素进行遍历和处理,无需显式使用循环结构。

collect

用于将流中的元素收集到一个容器或生成一个最终结果。它提供了许多常用的操作,以下是一些常见的collect操作:

  1. 将流元素收集到 ListArrayList

    List<Integer> list = stream.collect(Collectors.toList());
    
  2. 将流元素收集到 SetHashSet

    Set<Integer> set = stream.collect(Collectors.toSet());
    
  3. 将流元素收集到指定的集合类型:

    ArrayList<Integer> arrayList = stream.collect(Collectors.toCollection(ArrayList::new));
    
  4. 将流元素收集到 Map

    Map<Integer, String> map = stream.collect(Collectors.toMap(Employee::getId, Employee::getName));
    
  5. 将流元素分组,并将结果存储到 Map 中:

    可以使用 Stream 流进行分组操作。Stream 流提供了 groupingBy() 方法来实现分组。

    下面是一个示例代码,演示如何使用 Stream 流进行分组操作:

    import java.util.Arrays;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    public class GroupingExample {
        public static void main(String[] args) {
            // 创建一个包含多个字符串的列表
            List<String> strings = Arrays.asList("apple", "banana", "apricot", "blueberry", "avocado");
    
            // 使用 Stream 流进行分组,按照字符串的首字母进行分组
            Map<Character, List<String>> groupedMap = strings.stream()
                    .collect(Collectors.groupingBy(s -> s.charAt(0)));
    
            // 输出分组结果
            groupedMap.forEach((key, value) -> System.out.println(key + ": " + value));
        }
    }
    

    运行上述代码,将会按照字符串的首字母进行分组,并输出分组结果。输出结果如下:

    a: [apple, apricot, avocado]
    b: [banana, blueberry]
    

    在上述示例中,我们使用了 groupingBy() 方法,以字符串的首字母作为分组的依据。groupingBy() 方法接受一个函数,用于指定按照哪个属性或条件进行分组。最终,我们将分组结果保存在一个 Map 中,其中 key 为分组的标准(这里是首字母),value 为对应的元素列表。

  6. 将流元素分区,并将结果存储到 Map 中:

    假设我们有一个存储 Employee 对象的流,每个 Employee 对象包含 idname 属性,我们想要将员工按照是否为经理进行分区,将经理和非经理分别存储到一个 Map 中。我们可以使用 partitioningBy 来实现这个目标:

    Map<Boolean, List<Employee>> partitionedEmployees = stream.collect(Collectors.partitioningBy(Employee::isManager));
    

    在上述示例中,我们通过调用partitioningBy方法,并传入一个判断条件的方法引用(Employee::isManager),将流中的元素按照是否为经理进行分区。partitioningBy方法返回一个Map,其中键为Boolean类型,值为分区后的元素列表。

    假设我们的Employee类定义如下:

    class Employee {
        private int id;
        private String name;
        private boolean isManager;
    
        // 省略构造函数和其他方法
    
        public boolean isManager() {
            return isManager;
        }
    }
    

    假设流中包含以下员工对象:

    Employee emp1 = new Employee(1, "Alice", true);
    Employee emp2 = new Employee(2, "Bob", false);
    Employee emp3 = new Employee(3, "Charlie", true);
    Employee emp4 = new Employee(4, "David", false);
    

    执行partitioningBy操作后,将得到以下Map结果:

    {
        false=[Employee{id=2, name='Bob', isManager=false}, Employee{id=4, name='David', isManager=false}],
        true=[Employee{id=1, name='Alice', isManager=true}, Employee{id=3, name='Charlie', isManager=true}]
    }
    

    在结果的Map中,键为false的列表存储了非经理员工,键为true的列表存储了经理员工。

    通过使用partitioningBy操作,我们可以根据一个条件将流元素分成两个部分,并将分区的结果存储到一个Map中。这对于处理满足特定条件的元素非常有用,例如将数据分为两个不同的类别,根据条件进行过滤或处理。

  7. 将流元素连接成字符串:

    String result = stream.collect(Collectors.joining(", "));
    
  8. 将流元素统计为汇总信息:

    假设我们有一个存储Employee对象的流,每个Employee对象包含salary属性,我们想要统计员工的工资信息。我们可以使用summarizingInt来实现这个目标:

    IntSummaryStatistics statistics = stream.collect(Collectors.summarizingInt(Employee::getSalary));
    

    Collectors.summarizingInt 方法统计流中元素的汇总信息,并生成一个 IntSummaryStatistics 对象,包含了元素的总数、总和、平均值、最大值和最小值等统计结果。

    在上述示例中,我们调用summarizingInt方法,并传入一个获取工资的方法引用(Employee::getSalary)。summarizingInt方法返回一个IntSummaryStatistics对象,其中包含了流中元素的统计信息。

    假设我们的Employee类定义如下:

    class Employee {
        private int salary;
    
        // 省略构造函数和其他方法
    
        public int getSalary() {
            return salary;
        }
    }
    

    假设流中包含以下员工对象:

    Employee emp1 = new Employee(5000);
    Employee emp2 = new Employee(6000);
    Employee emp3 = new Employee(7000);
    Employee emp4 = new Employee(5500);
    

    执行summarizingInt操作后,将得到以下IntSummaryStatistics结果:

    IntSummaryStatistics{count=4, sum=23500, min=5000, average=5875.000000, max=7000}
    

    IntSummaryStatistics对象提供了以下方法用于获取统计信息:

    • getCount():返回元素的数量。
    • getSum():返回元素的总和。
    • getMin():返回元素的最小值。
    • getAverage():返回元素的平均值。
    • getMax():返回元素的最大值。

    通过使用summarizingInt操作,我们可以方便地获取流中元素的汇总信息,包括总数、总和、平均值、最大值和最小值。这对于统计数据或了解数据分布非常有用。

流元素分组和分区的区别

下面,我们单独说说第 5 点所说的将流元素分组,并将结果存储到 Map 中 和第 6 点将流元素分区,并将结果存储到 Map 中的区别。

第 5 点中的操作是将流元素根据一个属性或函数的结果进行分组,并将分组的结果存储到Map中。而第 6 点中的操作是根据一个条件将流元素进行分区,并将分区的结果存储到 Map 中。虽然两个操作都使用了Collectors类的方法,并将结果存储到Map中,但它们的目的和用法略有不同。

在第 5 点中,我们使用 Collectors.groupingBy 方法将流元素按照一个属性或函数的结果进行分组,生成一个 Map,其中键为分组的依据,值为分组后的元素列表。这样的分组操作适用于将数据按照某个属性进行分类或归类,例如根据部门将员工进行分组。

在第 6 点中,我们使用 Collectors.partitioningBy 方法根据一个条件将流元素进行分区,生成一个 Map,其中键为 Boolean 类型,值为分区后的元素列表。这样的分区操作适用于将数据根据一个条件进行分为两个部分,例如将员工按照是否为经理进行分区。

区别总结如下:

  • groupingBy:根据属性或函数的结果进行分组,生成一个Map,键为分组依据,值为分组后的元素列表。
  • partitioningBy:根据条件进行分区,生成一个Map,键为Boolean类型,值为分区后的元素列表。

因此,这两个操作在分组和分区的概念上略有不同,但都可以将流元素按照某种方式进行分类,并将结果存储到Map中以供后续处理。具体使用哪种操作取决于业务需求和分组/分区的条件。

summarizingInt 与 summingInt

在第 8 点中,我们使用 summarizingInt 将流元素统计为汇总信息。Collectors 还有一个很类似的方法 summingInt,我们来聊聊它们之间的区别:

  1. summarizingInt操作使用IntSummaryStatistics类来收集统计信息,它提供了更全面的汇总结果,包括总数、总和、平均值、最大值和最小值等。它返回的是一个IntSummaryStatistics对象,可以通过该对象的方法获取统计结果。
  2. summingInt操作只计算元素的总和,返回的是一个Integer类型的结果,表示所有元素的累加和。

下面是两个操作的示例用法和结果对比:

使用summarizingInt进行统计:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
IntSummaryStatistics statistics = numbers.stream().collect(Collectors.summarizingInt(Integer::intValue));

IntSummaryStatistics结果:

IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}

使用summingInt进行统计:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().collect(Collectors.summingInt(Integer::intValue));

sum结果:

15

总结:

  • summarizingInt提供了更全面的汇总信息,包括总数、总和、平均值、最大值和最小值等。
  • summingInt仅计算元素的总和,返回一个整数结果。

选择使用哪种操作取决于你需要的统计信息。如果你需要更详细的统计结果,包括平均值、最大值和最小值等,那么使用summarizingInt更适合。如果只需要计算元素的总和,那么使用summingInt更简洁高效。

reduce

reduce 用于将流中的元素进行累积、组合或聚合操作,生成一个最终的结果。它接受一个二元操作符(BinaryOperator)作为参数,用于指定元素的累积逻辑。

下面是一个使用 reduce 操作的示例:

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

// 使用 reduce 操作求和
int sum = numbers.stream()
                 .reduce(0, (a, b) -> a + b);

System.out.println(sum);

在上述示例中,我们有一个包含整数的列表 numbers。我们使用 reduce 操作将这些整数求和。初始值为 0,累积操作为将两个整数相加。

执行结果将输出:

15

在这个例子中,初始值为 0。reduce 操作遍历流中的元素,将初始值和流中的第一个元素相加得到中间结果,然后将中间结果和流中的下一个元素相加,以此类推,直到遍历完所有元素。最后的结果就是所有元素的和。

reduce 操作还有其他的用法,例如用于寻找最大值、最小值、拼接字符串等。通过提供不同的累积操作,可以根据具体需求进行自定义的累积逻辑。

注意,reduce 操作的结果是一个 Optional 类型,因为流可能为空。如果流为空,那么 reduce 操作的结果将是一个空的 Optional。如果你确定流不会为空,也可以使用无初始值的重载方法 reduce(BinaryOperator accumulator),此时结果将直接是一个具体的值。

总结:reduce 操作用于将流中的元素进行累积、组合或聚合操作。它接受一个二元操作符作为参数,并根据累积逻辑对流中的元素进行操作,生成最终的结果。

min

当使用 min 操作时,Stream 流会根据给定的比较器(Comparator)或元素的自然顺序,返回流中的最小元素。下面是一个使用 min 操作找到最小整数的例子:

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

// 找到最小整数
Optional<Integer> minNumber = numbers.stream()
                                     .min(Integer::compareTo);

if (minNumber.isPresent()) {
    System.out.println("最小整数是: " + minNumber.get());
} else {
    System.out.println("流为空,无最小整数。");
}

在上述示例中,我们有一个包含整数的列表 numbers。我们使用 min 操作查找列表中的最小整数。我们传递 Integer::compareTo 作为比较器来确定最小值。

执行结果将输出:

最小整数是: 1

在这个例子中,min 操作将遍历流中的元素,并使用比较器或自然顺序来找到最小的整数。返回的结果是一个 Optional 类型,因为流可能为空。我们可以使用 isPresent() 方法来检查结果是否存在,并使用 get() 方法获取最小值。

如果流为空,那么 min 操作的结果将是一个空的 Optional

除了整数,min 操作也适用于其他类型的元素,只需要提供适当的比较器或确保元素的类型实现了 Comparable 接口。

总结:min 操作用于找到流中的最小元素,根据给定的比较器或元素的自然顺序进行比较。它返回一个 Optional 类型的结果,可以使用 isPresent()get() 方法来检查和获取最小值。

max

当使用 max 操作时,Stream 流会根据给定的比较器(Comparator)或元素的自然顺序,返回流中的最大元素。下面是一个使用 max 操作找到最大整数的例子:

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

// 找到最大整数
Optional<Integer> maxNumber = numbers.stream()
                                     .max(Integer::compareTo);

if (maxNumber.isPresent()) {
    System.out.println("最大整数是: " + maxNumber.get());
} else {
    System.out.println("流为空,无最大整数。");
}

在上述示例中,我们有一个包含整数的列表 numbers。我们使用 max 操作查找列表中的最大整数。我们传递 Integer::compareTo 作为比较器来确定最大值。

执行结果将输出:

最大整数是: 9

在这个例子中,max 操作将遍历流中的元素,并使用比较器或自然顺序来找到最大的整数。返回的结果是一个 Optional 类型,因为流可能为空。我们可以使用 isPresent() 方法来检查结果是否存在,并使用 get() 方法获取最大值。

如果流为空,那么 max 操作的结果将是一个空的 Optional

除了整数,max 操作也适用于其他类型的元素,只需要提供适当的比较器或确保元素的类型实现了 Comparable 接口。

总结:max 操作用于找到流中的最大元素,根据给定的比较器或元素的自然顺序进行比较。它返回一个 Optional 类型的结果,可以使用 isPresent()get() 方法来检查和获取最大值。

sum

Java的流(Stream)API 提供了 sum() 方法,可用于对流中的数值类型进行求和。

下面是一个简单的示例,演示了如何使用流(Stream)来对整数列表进行求和:

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

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

        int sum = numbers.stream()
                        .mapToInt(Integer::intValue)
                        .sum();

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

在上述示例中,我们首先创建了一个整数列表 numbers,包含了 1 到 5 的整数。然后,我们使用 stream() 方法将列表转换为流(Stream)。接下来,我们使用mapToInt() 方法将流中的元素映射为整数类型。最后,我们使用 sum() 方法对整数流进行求和,并将结果存储在 sum 变量中。最后,我们打印出求和的结果。

请注意,使用 mapToInt() 方法将流转换为 IntStream 可以提高求和操作的性能,因为 IntStream 是针对整数类型的专门优化的流。

并行流

Java Stream 提供了 parallel() 方法,用于将流转换为并行流。并行流可以自动将操作并行化,利用多线程处理数据,从而提高处理速度。但是需要谨慎使用,并行流可能引发线程安全问题和性能损失。

要创建并行流,只需在流上调用 parallel() 方法即可,示例如下:

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

Stream<Integer> sequentialStream = numbers.stream();      // 创建串行流
Stream<Integer> parallelStream = numbers.parallelStream(); // 创建并行流

在上述示例中,我们使用 stream() 方法创建了一个串行流 sequentialStream,使用 parallelStream() 方法创建了一个并行流 parallelStream

并行流的使用方式与串行流类似,可以对其应用各种中间操作(如 filtermapreduce 等)和终端操作(如 forEachcollectcount 等)来处理数据。

并行流的优势在于它可以充分利用多核处理器的并行计算能力,适用于大规模数据的处理。在某些场景下,使用并行流可以显著提升处理速度。

然而,使用并行流也需要注意一些问题:

  1. 并行流适用于执行耗时的操作,当操作简单且数据量较小时,并行化的开销可能会超过并行计算的收益。
  2. 并行流在处理过程中会涉及到多线程的调度和同步,因此需要确保代码是线程安全的。
  3. 某些操作(如有状态的中间操作)可能会导致并行流的性能下降,需要谨慎使用。
  4. 并行流的处理顺序可能与串行流不同,因为操作会以并发的方式执行。

以下是一个使用并行流的真实案例:计算列表中所有数字的平方和。

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

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

        // 串行流计算平方和
        int sequentialSum = numbers.stream()
                                   .mapToInt(n -> n * n)
                                   .sum();

        // 并行流计算平方和
        int parallelSum = numbers.parallelStream()
                                 .mapToInt(n -> n * n)
                                 .sum();

        System.out.println("串行流平方和: " + sequentialSum);
        System.out.println("并行流平方和: " + parallelSum);
    }
}

在上述示例中,我们有一个包含整数的列表 numbers。首先,我们使用串行流 stream() 对列表进行处理,将每个元素映射为其平方值,然后使用 mapToInt 将流转换为 IntStream,最后使用 sum() 方法计算平方和。

接下来,我们使用并行流 parallelStream() 对列表进行相同的处理操作,通过调用 parallelStream() 方法将流转换为并行流,并使用相同的操作链计算平方和。

运行以上代码,会得到以下输出:

串行流平方和: 55
并行流平方和: 55

可以看到,无论是使用串行流还是并行流,最终计算的平方和结果都是相同的。

基于 Spring Boot 2.7.12、MyBatis-Plus、Spring Security 等主流技术栈构建的后台管理系统:
后台:https://gitee.com/linjiabin100/pi-admin.git
前端:https://gitee.com/linjiabin100/pi-admin-web.git