Java函数式编程之【流(Stream)性能优化

  • 一、流(Stream)性能优化的预备知识
    • (一)并行与并发的区别
    • (二)Stream操作特性分类
    • (三)Stream流管道的相关知识
  • 二、流(Stream)的性能优化策略
    • (一)选择合适的Stream(流)类型
      • 1,优先使用基本数据类型流(避免装箱/拆箱开销)
      • 2,正确选择 顺序流(stream) 或 并行流(parallelStream)
    • (二)中间操作的优化
    • (三)终端操作的优化
    • (四)正确使用并行流(parallelStream)
  • 三、总结:流(Stream)性能优化的工作流程

一、流(Stream)性能优化的预备知识

我们先来详细探讨一下基于 Java Stream API 的函数式编程时性能调优相关的一些基础知识。

(一)并行与并发的区别

在Java函数式编程中编写并行程序很容易,要优化Java Stream程序的性能首先要搞清楚并行和并发的概念。

并发 (Concurrency):多个任务在同一时间段内执行,但不一定是同时执行。例如:可在单核CPU上并发运行多个线程。其执行方式是通过时间片轮转,进行任务切换实现。
在单核CPU上的并发执行,逻辑上是"同时"执行,实质是串行轮流执行的。

并行 (Parallelism):多个任务同时执行,但需要有多核或多处理器硬件支持。例如:多核CPU同时处理多个不同任务。

(二)Stream操作特性分类

我们先来了解一下Stream操作分类,因为弄清Stream操作分类对大数据的高效迭代处理的性能优化有很大帮助。

中间操作和终止操作是Stream API中的关键概念。中间操作又可以细分为无状态(Stateless)操作和有状态(Stateful)操作;终结操作又可细分为短路(Short-circuiting)操作和非短路(Unshort-circuiting)操作。

  • 中间操作:又称为装饰操作,它的输入是一个Stream,操作后返回一个新的Stream。

中间操作只对操作进行了声明,它会把Stream从一种流(Stream)转换为另一种流,中间操作都是惰性的,它们不会立即执行。例如,filter(), map() 和 sorted() 等方法都属于中间操作,它们会返回一个新的Stream实例。中间操作可以链接在一起,形成一个链式调用,成为Stream处理管道的组成部分。

无状态(Stateless)操作和有状态(Stateful)操作:
中间操作又可以分为无状态(Stateless)与有状态(Stateful)操作,前者是指元素的处理不受之前元素的影响,后者是指该操作只有拿到所有元素之后才能继续下去。
1,常见无状态(Stateless)的中间操作有:filter() 、map()、 flatMap()、 peek()
2,常见有状态(Stateful)的中间操作有:distinct() 、sorted()、 limit() 、skip()

  • 终止操作:又称为终端操作,终止操作将触发了整个流(Stream)的计算过程,得到操作结果。

终止操作则是主动求值操作。终止操作会触发整个Stream处理管道的执行。例如,collect(), forEach(), reduce() 等方法都是终止操作。
当终止操作被调用时,所有的中间操作将按序执行,最终得到操作结果。

短路(Short-circuiting)操作和非短路(Unshort-circuiting)操作:
终结操作又可以分为短路(Short-circuiting)与非短路(Unshort-circuiting)操作,前者是指遇到某些符合条件的元素就可以得到最终结果,后者是指必须处理完所有元素才能得到最终结果。

短路(Short-circuiting): anyMatch()、 allMatch()、 findFirst()、 findAny()、 noneMatch()
非短路(Unshort-circuiting)操作:forEachOrdered()、toArray()、reduce()、collect()、max()、min()、count()

(三)Stream流管道的相关知识

函数式编程的一个流管道(Stream pipeline),包括Stream的创建(由数据源创建Stream)、0个或n个Stream的中间操作和一个Stream的终止操作。
在这里插入图片描述
如果不了解 流(Stream) 函数式编程的内部机制,就写不错高性能的程序。只有了解了流管道(Stream pipeline)的执行过程,我们才能更好地优化Stream的性能。

我们先来梳理一下 Stream API 是由哪些主要接口和类组合而成的呢?BaseStream 和 Stream 是最顶端的接口类。

  • BaseStream接口主要定义了流的基本方法,例如,iterator()、spliterator()、isParallel()等;
  • Stream接口则定义了一些流的常用操作方法,例如,filter()、map()、mapToLong()、flatMap()和collect()等。
  • ReferencePipeline是一个抽象类,他通过定义内部类组装了各种流的操作。它定义了Head、StatelessOp、StatefulOp三个内部类,实现了BaseStream与Stream的接口,继承了它们的方法。
  • Sink接口则定义了每个Stream操作之间关系的协议,它包含begin()、end()、cancellationRequested()、accpet()四个方法。ReferencePipeline最终会将整个Stream流操作组装成一个调用链,而这条调用链上的各个Stream操作的上下关系就是通过Sink接口协议来定义实现的。

Stream操作的链接调用
一个流管道(Stream pipeline)可由Stream的各种操作组合而成,并最终由终止操作完成数据处理。
在JDK中每次的中间操作会用阶段(Stage)命名。

流管道的链接调用结构通常是由ReferencePipeline类实现的,前文已介绍过ReferencePipeline包含了Head、StatelessOp、StatefulOp三种内部类。

来看一个简单的示例:

package stream;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
public class StreamDemo {public static void main(String[] args) {//字符串的"+"操作   //字符串拼接List<String> names = Arrays.asList("Bob","John","Mary");Optional<String> str = names.stream().filter(e->e.length()>=4).reduce((s1,s2)->s1+s2);System.out.println("拼接结果:"+str.orElse(""));}
}

Head类主要用来定义数据源操作,在初次调用names.stream()方法时,会初次加载Head对象,此时为加载数据源操作;接着加载的是中间操作,分别为无状态中间操作StatelessOp对象和有状态操作StatefulOp对象,此时的Stage并没有执行,而是通过AbstractPipeline生成了一个中间操作Stage链表;当我们调用终止操作时,会生成一个最终的Stage,通过这个Stage触发之前的中间操作,从最后一个Stage开始,递归产生一个Sink链。

二、流(Stream)的性能优化策略

(一)选择合适的Stream(流)类型

1,优先使用基本数据类型流(避免装箱/拆箱开销)

这是最立竿见影的优化策略之一。因为 Stream<Integer>Stream<Long>Stream<Double> 等都属于对象流 Stream<T>IntegerLongDouble 都是基本数据类型的包装类,包装类型的对象流 Stream<T> 会带来巨大的 装箱(Boxing)拆箱(Unboxing) 开销。

将基本数据类型(如 int、long或double)转换为数值的包装类型(如 Integer、Long和Double)称为装箱操作;反之称为拆箱操作。
对于大数据的数值运算,装箱和拆箱的计算开销,以及包装类型占用的额外存储空间,会明显降低程序的运算速度。

优化策略: 在大数据的数值计算和数理统计场景,对于 int, long, 和 double 等基本数据类型,应优先使用基本数据类型流 IntStream, LongStream 和 DoubleStream,以避免装箱和拆箱的开销。

这些流有一个最常用的汇总统计SummaryStatistics()方法,这个方法可得到一组统计数据:元素个数、汇总值、最大值、最小值和平均值。
Java语言为基本数据类型流专门内置了一些实用的方法:例如,都有内置的规约操作,包括average、count、max、min、sum。
这些方法都直接在原始数据类型上操作,避免了包装类的装箱和拆箱开销。

当数据源为大数据的列表 List<Integer> 时的情形:

	// 低效 - 存在装箱开销List<Integer> numbers = ...;int sum = numbers.stream().reduce(0, Integer::sum);// 高效 - 使用 IntStreamint sum = numbers.stream().mapToInt(Integer::intValue) // 转换为 IntStream.sum(); // 直接在 int 上操作

更进一步: 如果数据源本身是基本数据类型的数组的情形,可直接使用 Arrays.stream() 创建Stream<Integer> 流:

	int[] intArray = ...;IntStream intStream = Arrays.stream(intArray);

装箱和不装箱的性能比较测试例程:

	public static void 装箱与不装箱Test() {// 装箱版本测试long startTime = System.currentTimeMillis();List<Integer> list = new ArrayList<>();for (int i = 0; i < 10_000_000; i++) {list.add(i);}long sum = 0;for (Integer num : list) {sum += num;}long endTime = System.currentTimeMillis();long boxedTime = endTime - startTime;// 原始数据类型版本测试startTime = System.currentTimeMillis();int[] array = new int[10_000_000];for (int i = 0; i < array.length; i++) {array[i] = i;}sum = 0;for (int num : array) {sum += num;}endTime = System.currentTimeMillis();long primitiveTime = endTime - startTime;System.out.println("装箱版本耗时: " + boxedTime + "ms");System.out.println("原始数据类型版本耗时: " + primitiveTime + "ms");}

装箱与不装箱的测试结果:
在这里插入图片描述
在大数据计算密集型任务中,避免装箱操作通常可以获大幅的性能提升,同时减少内存使用和GC压力。

2,正确选择 顺序流(stream) 或 并行流(parallelStream)

顺序流或并行流:根据数据集大小和是否对元素顺序敏感选择顺序流stream()或并行流parallelStream()。小规模数据集或顺序敏感的操作适合顺序流;大规模数据集且能接受非确定性结果时适合并行流。
在这里插入图片描述

下面来看一个小规模数据集,我们分别使用基本数据类型流、顺序流装箱和并行流装箱进行比较测试:

package stream;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class StreamTest {public static void primitive() { //基本数据类型流(顺序流)long start = System.currentTimeMillis();long sum = IntStream.range(1, 10_000).sum();long duration = System.currentTimeMillis() - start;System.out.println("基本类型流求和结果: " + sum + ", 耗时: " + duration + "ms");}public static void boxed() { //顺序流装箱long start = System.currentTimeMillis();List<Integer> numbers = IntStream.range(1, 10_000).boxed().collect(Collectors.toList());int sum = numbers.stream().mapToInt(Integer::intValue).sum();long duration = System.currentTimeMillis() - start;System.out.println("顺序流求和结果: " + sum + ", 耗时: " + duration + "ms");}public static void parallel() { //并行流装箱long start = System.currentTimeMillis();List<Integer> numbers = IntStream.range(1, 10_000).boxed().collect(Collectors.toList());int sum = numbers.parallelStream().mapToInt(Integer::intValue).sum();long duration = System.currentTimeMillis() - start;System.out.println("并行流求和结果: " + sum + ", 耗时: " + duration + "ms");}public static void main(String[] args) {primitive();boxed();parallel();}
}

小规模数据集,我们分别使用基本数据类型流、顺序流装箱和并行流装箱进行比较测试。
预期结果是耗时从小到大。但是令人遗憾的是,测试并未得到预期的结果。
如果调整代码顺序会得到完全不同的测试结果。测试顺序严重影响结果。

原因是:Java 的 JIT(即时编译器)需要时间来优化代码。先执行的代码都会因为JIT还没有充分优化而变慢。

  • 先执行的代码:承担 JIT 编译的开销。
  • 后执行的代码:享受已经优化好的代码。

解决方案:正确的性能测试方法是使用专业的基准测试工具,如JMH(Java Microbenchmark Harness)。

下面这个示例,试图演示在数值运算场景中,基本数据类型流LongStream 比对象流Stream有更好的效率。
使用专业的基准测试工具(如 JMH),才能得到准确的结果。你会发现 LongStream 确实比 Stream 快 2-5 倍。

package stream;
import java.util.Random;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class BOxUnboxingComparison {private static final int DATA_SIZE = 10_000;private static final Random RANDOM1 = new Random(42);private static final Random RANDOM2 = new Random(42);public static void main(String[] args) {// 测试1: 基本的统计操作System.out.println("=== 基本统计操作性能测试 ===");/***对象流Stream(Long),数据计算时需要拆箱***/long startTime = System.nanoTime();long boxedSum = Stream.generate(() -> Math.abs(RANDOM1.nextLong()) % 100).limit(DATA_SIZE).mapToLong(Long::longValue)  // 必须拆箱才能计算.sum();long boxedTime = System.nanoTime() - startTime;/***基本数据类型流LongStream,数据计算时无须拆箱***/startTime = System.nanoTime();long primSum = LongStream.generate(() -> Math.abs(RANDOM2.nextLong()) % 100).limit(DATA_SIZE).sum();long primTime = System.nanoTime() - startTime;printResults("求和", primTime, boxedTime, primSum, boxedSum);// 测试2: 过滤和聚合操作System.out.println("\n=== 过滤和聚合操作性能测试 ===");/***对象流Stream(Long),数据计算时需要拆箱***/startTime = System.nanoTime();double boxedAvg = Stream.generate(() -> Math.abs(RANDOM1.nextLong()) % 100).limit(DATA_SIZE).filter(x -> x > 50)        // 过滤(自动拆箱比较).mapToLong(Long::longValue)  // 必须拆箱.average().orElse(0);boxedTime = System.nanoTime() - startTime;/***基本数据类型流LongStream,数据计算时无须拆箱***/startTime = System.nanoTime();double primAvg = LongStream.generate(() -> Math.abs(RANDOM2.nextLong()) % 100).limit(DATA_SIZE).filter(x -> x > 50)        // 过滤.average()                   // 计算平均值.orElse(0);primTime = System.nanoTime() - startTime;printResults("平均值", primTime, boxedTime, primAvg, boxedAvg);}private static void printResults(String operation, long primitiveTime, long boxedTime, Object primitiveResult, Object boxedResult) {System.out.printf("LongStream    - 耗时: %8d ns, 结果: %s%n", primitiveTime, primitiveResult);System.out.printf("Stream<Long>  - 耗时: %8d ns, 结果: %s%n", boxedTime, boxedResult);System.out.printf("性能差距: %.2f 倍%n", (double) boxedTime / primitiveTime);}
}

(二)中间操作的优化

每个中间操作都可能涉及到对元素的复制和创建新的Stream对象。合理配置中间操作可优化Stream的性能。

  • 使用 distinct() 删除重复元素

如果 stream 中可能包含重复元素,可使用 distinct() 操作将其删除,防止无效处理以提高性能。

		List<Integer> list = Arrays.asList(1, 2, 3, 3, 4, 5, 5);list.stream().distinct().forEach(System.out::println);
  • 合理配置 limit() 防止无效处理

示例:

	// 生成斐波那契数列但只取前10个Stream.iterate(new int[]{0, 1}, fib -> new int[]{fib[1], fib[0] + fib[1]}).limit(10) // 限制器.map(fib -> fib[0]).forEach(System.out::println);
  • 谨慎使用 sorted()

sorted()操作可能很昂贵,尤其是对于大数据流(large streams)。请谨慎使用,只有在必要时才使用。如果知道输入数据已经排序,可以跳过此操作。

  • 选择正确的中间操作顺序。需要数据过滤的场景,尽早使用filter()

中间操作的顺序对性能有巨大影响,尤其是 filter() 和 map() 的顺序。
优化策略
合理配置map()和filter()的组合。先过滤filter(),后映射map(): 尽可能早地使用 filter() 筛选掉不需要处理的元素。这会使后续所有操作(map, sorted, collect 等)的数据量变小。

示例:

	var list = Arrays.asList(1, 2, 3, 4, 5,8,16,18);var filteredList = list.stream().filter(i -> i % 2 == 0).map(i -> i * 2).collect(Collectors.toList());

上面这个示例“在 map() 之前使用 filter()”可过滤掉一大半的元素,大大减少了map()操作的工作量。

避免在中间操作中执行昂贵操作: 如果 map 中包含一个昂贵的方法调用,确保它只在必要的元素上执行(即先被 filter 处理过的)。

	// 低效的中间操作顺序List<String> names = list.stream().map(e -> expensiveOperation(e)) // 对所有元素执行昂贵操作.filter(s -> s.length() > 3).collect(Collectors.toList());// 高效的中间操作顺序 - 优先过滤List<String> names = list.stream().filter(e -> e.length() > 3) // 先减少数量.map(e -> expensiveOperation(e)) // 只对过滤后的元素执行.collect(Collectors.toList());

再来看另一个典型示例:

// 目标:获取所有字符串长度的平方,且只保留大于10的结果// 低效:先map所有,再filter
List<Integer> lengthsSquared = words.stream().map(String::length) // 所有元素都被映射.map(len -> len * len) // 所有元素再次被映射.filter(sq -> sq > 10) // 最后过滤掉大部分.collect(Collectors.toList());// 更高效:合并map和filter,但逻辑稍复杂
// (有时无法避免,但这里可以优化)// 最优:合并map操作,并尽早filter
List<Integer> lengthsSquared = words.stream().map(String::length) // 先map一次.filter(len -> len > 3) // 尽早过滤:因为如果length<=3,平方肯定<=9,不会大于10.map(len -> len * len) // 只为剩余的元素进行平方计算.collect(Collectors.toList());
  • 适当合并中间操作减少Stream内部迭代次数

1,多个连续的 filter() 操作合并。
经典示例:筛选用户列表
合并多个filter操作是一个非常重要且常见的优化策略。它的核心思想是:通过逻辑运算符将多个过滤条件合并为一个filter,减少Stream管道中的阶段数量和 predicate(断言)的执行次数。
假设我们有一个User对象列表,我们需要筛选出同时满足以下条件的用户:
(1), 年龄大于等于18岁(成年人)
(2), 年龄小于65岁(未退休)
(3), 账户状态是激活的(ACTIVE)
(4), 登录次数大于0(活跃用户)

低效算法: 使用多个连续的 filter() 操作
在这个算法中,数据仿佛要“过四道筛子”:

List<User> users = // ... 获取用户列表List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18)        // 第一轮过滤:检查条件1.filter(user -> user.getAge() < 65)         // 第二轮过滤:检查条件2.filter(user -> user.getStatus() == User.Status.ACTIVE) // 第三轮过滤:检查条件3.filter(user -> user.getLoginCount() > 0)   // 第四轮过滤:检查条件4.collect(Collectors.toList());

高效做法:合并 filter() 操作

List<User> users = // ... 获取用户列表List<User> filteredUsers = users.stream().filter(user -> user.getAge() >= 18 && user.getAge() < 65&& user.getStatus() == User.Status.ACTIVE&& user.getLoginCount() > 0).collect(Collectors.toList());

2,多个可合并的映射(map)操作合并
将多个可合并的映射(map)操作合并为一个,从而减少Stream管道中需要执行的迭代次数和中间对象的创建。

经典示例:处理字符串列表,假设我们有一个字符串列表,我们需要:
(1)将每个字符串转换为大写。
(2)反转每个字符串。
(3)获取每个字符串的前3个字符。

低效做法:使用多个连续的 map 操作。
在这个算法中,Stream管道内部发生了3次完整的迭代,并创建了2个中间的Stream元素集合。

List<String> words = Arrays.asList("hello", "world", "java", "stream");List<String> result = words.stream().map(String::toUpperCase)      // 第一次迭代:生成新流 ["HELLO", "WORLD", "JAVA", "STREAM"].map(s -> new StringBuilder(s).reverse().toString()) // 第二次迭代:生成新流 ["OLLEH", "DLROW", "AVAJ", "MAERTS"].map(s -> s.substring(0, Math.min(3, s.length())))   // 第三次迭代:生成新流 ["OLL", "DLR", "AVA", "MAE"].collect(Collectors.toList());System.out.println(result); // 输出: [OLL, DLR, AVA, MAE]

高效做法:合并中间操作

List<String> words = Arrays.asList("hello", "world", "java", "stream");List<String> result = words.stream().map(s -> { // 只进行一次映射操作,将所有转换逻辑合并String upper = s.toUpperCase();String reversed = new StringBuilder(upper).reverse().toString();return reversed.substring(0, Math.min(3, reversed.length()));}).collect(Collectors.toList());System.out.println(result); // 输出: [OLL, DLR, AVA, MAE]

(三)终端操作的优化

  • 选择高效的终端操作

不同的终端操作有不同的性能特征。优化策略:
(1)、anyMatch/allMatch/noneMatch 是短路(short-circuiting)操作,一旦找到答案就会停止处理,性能通常很好。
(2)、findFirst 在并行流中需要协调,可能会有一定开销。如果顺序不重要,findAny 在并行流中限制更少,性能可能更高。
(3)、count 对于 SIZED 流(如从 List 创建的流)来说非常高效。否则,它需要遍历所有元素。
(4)、collect 的性能取决于你使用的收集器。Collectors.toList() 通常很高效。Collectors.groupingBy 和 Collectors.toMap 的成本较高,需要注意。

流管道(Stream pipeline)中的操作应该都是无副作用的。
例如:在中间操作中修改外部状态会导致并发问题(尤其在并行流中)和不可预测的行为。

终端操作也应该是无副作用的。应优先使用收集器(Collectors) 而不是在 forEach 中修改外部集合。forEach 应仅用于消费最终结果,而不是处理过程。请看示例:

// 错误 - 在 forEach 中修改外部集合(非线程安全)
List<String> result = new ArrayList<>();
stream.filter(...).forEach(e -> result.add(e)); // 在并行流中会崩溃// 正确 - 使用收集器
List<String> result = stream.filter(...).collect(Collectors.toList()); // 线程安全且高效
  • 利用短路操作提高效率
    诸如 findFirst(),findAny() 和 anyMatch() 等短路操作,在找到符合条件或不符合条件时就会立即返回,不处理整个流。利用这些操作可以提高性能。
/***权限检查短路***/
List<Permission> userPermissions = getUserPermissions(userId);
// 检查用户是否有管理员权限
boolean isAdmin = userPermissions.stream().anyMatch(p -> p.getType() == PermissionType.ADMIN); // 找到第一个管理员权限就返回
// 比收集到列表再判断更高效/***验证输入数据***/
List<String> inputs = getInputs();
// 验证所有输入是否有效(遇到第一个无效就停止)
boolean allValid = inputs.stream().peek(input -> System.out.println("Validating: " + input)).allMatch(this::isValidInput);

(四)正确使用并行流(parallelStream)

Java的Stream API 支持并行流(parallelStream)操作,可以利用多个处理器阵列或多核处理器并行处理数据。
并行流操作通过将数据分割成多个子集,然后并行处理,最终合并结果。

并行流(parallelStream)可以在处理大量数据时提供更好的性能,但也会带来额外开销和竞争条件。谨慎使用parallelStream,并需要考虑数据规模、操作复杂程度和可用处理器数量等因素。

并行操作引入了线程管理的开销和线程间协调开销。如果使用不当,也可能引起线程管理的开销增加。例如:在处理 IO 密集型任务或数据量小的场景,并行流可能因线程管理和线程间协调的开销反而更慢。

当调用 stream().parallel() 或 parallelStream() 时进行并行处理时,Java 会将数据分割成多个子集,由 ForkJoinPool 分配线程并行处理。这种机制在处理“大数据集、CPU 密集型操作”时优势明显,比如统计分析、数据汇总,以及排序、过滤、聚合等操作。

并行流需要有硬件支持:多个处理器或多核处理器的系统

并行流 (parallelStream) 可以将工作负载分配到多个 CPU 核心上,但并非万能药。错误使用会导致更差的性能(线程上下文切换开销)甚至错误结果。

优化策略:

使用工具测量,不要凭猜测: 始终使用类似 JMH 的工具进行基准测试,比较并行和串行流的性能。并行化的开销(拆分、协调、合并)只有在数据量足够大、计算足够密集时才能被抵消。

并行流(parallelStream)适用场景:

  • 大数据,大规模数据源(例如,数十万以上元素)。
  • 每个元素的处理是计算密集型(CPU-intensive)的,而不是 I/O 密集型(如网络请求、文件读写)。
  • 数据源易于拆分:ArrayList、数组、IntStream.range() 等支持随机访问的数据源拆分效率极高。而 LinkedList、Stream.iterate() 则难以有效拆分。
  • 操作是无状态且无关联的(如 map, filter),合并操作成本低(如 reduce 的合并器)。

不适合的场景:

  • 数据量小。
  • 操作是 I/O 密集型(线程会大部分时间在等待,浪费资源,应使用专门的异步 API如 CompletableFuture)。
  • 操作有状态或有副作用(如 sorted, distinct, limit 在并行流中代价更高,因为需要协调)。
  • 使用了有状态的 Lambda 表达式或阻塞操作。

示例:

	List<String> hugeList = ...;// 好的并行用例:大数据集,计算密集型操作long count = hugeList.parallelStream().filter(s -> s.length() > 10) // 无状态.count(); // 简单合并// 坏的并行用例:小数据集long count = smallList.parallelStream() // 开销大于收益.count();

三、总结:流(Stream)性能优化的工作流程

  • 首先,写出正确、清晰的程序代码: 不要过早优化。先使用 Stream API 实现正确的逻辑。

  • 基准测试: 使用 JMH (Java Microbenchmark Harness) 等工具来识别真正的性能瓶颈。不要靠猜测。

  • 应用调优策略进行性能优化:

第一步: 检查是否能优先使用基本数据类型流 (IntStream 等)。

第二步: 中间操作的优化,检查中间操作顺序,例如确保尽早过滤 (filter)。

第三步: 评估是否适合并行化 (parallelStream),并进行测试。

第四步(高级): 检查终端操作和收集器,必要时考虑自定义。

再次测量: 每次优化后都进行基准测试,确认优化确实有效。

通过遵循这些策略,才能最大限度地发挥 Java Stream API 函数式编程的威力,同时保持代码的高性能。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/97381.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/97381.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/97381.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Cybero: 1靶场渗透

Cybero: 1 来自 <Cybero: 1 ~ VulnHub> 1&#xff0c;将两台虚拟机网络连接都改为NAT模式 2&#xff0c;攻击机上做namp局域网扫描发现靶机 nmap -sn 192.168.23.0/24 那么攻击机IP为192.168.23.128&#xff0c;靶场IP192.168.23.139 3&#xff0c;对靶机进行端口服务探…

【学习笔记】非异步安全函数(禁止在信号处理中调用)

非异步安全函数&#xff08;禁止在信号处理中调用&#xff09; 一、测试 在信号处理函数&#xff08;Signal Handler&#xff09;中&#xff0c;只有异步信号安全函数&#xff08;async-signal-safe functions&#xff09; 可以安全调用。这类函数的特点是&#xff1a;不使用全…

【K8s】整体认识K8s之K8s的控制器

作用&#xff1a;控制器的作用就是持续监控k8s集群的状态&#xff0c;让它处于我们期望的状态&#xff0c;常见的控制器有replicaset、deployment、daemonset、statefulset 、job 、cronjobReplicaset控制一组pod的副本数&#xff0c;始终与预设的值相同&#xff0c;会持续监视…

R ggplot2学习Nature子刊一张图,换数据即可用!

本次使用R语言复现Nature Communications上的1张组合图,这张图兼具颜值+节约版面! Fig. 1 b原图 ❤️复现效果图-b图❤️ ✅读入测试数据! ✅关键代码, # 关键代码 library(ggplot2) library(dplyr) library(cowplot)# --- 外圈图 --- p_outer <- ggplot(data_aug, aes…

迷你电脑用到什么型号的RJ45网口

迷你电脑常用的 RJ45 网口主要有标准 RJ45 网口和 Mini RJ45 网口两种。标准 RJ45 网口是最常见的类型&#xff0c;遵循 IEEE 802.3i 标准&#xff0c;采用 8P8C&#xff08;8 Position 8 Contact&#xff0c;8 位 8 触点&#xff09;连接器&#xff0c;有 T568A 和 T568B 两种…

网络安全 | 保护智能家居和企业IoT设备的安全策略

网络安全 | 保护智能家居和企业IoT设备的安全策略 一、前言 二、智能家居和企业 IoT 设备面临的安全威胁 2.1 设备自身安全缺陷 2.2 网络通信安全隐患 2.3 数据隐私风险 2.4 恶意软件和攻击手段 三、保护智能家居和企业 IoT 设备的安全策略 3.1 设备安全设计与制造环节的考量 3…

优化器全指南:从原理到调优实战

本文将带你轻松理解深度学习中的“导航系统”——优化器。我们会避开复杂的数学公式,用大量的比喻和图示,让你彻底明白 Adam、AdamW、LAMB 是怎么回事,并学会如何调节它们的关键参数。 第一部分:核心概念:优化器是什么? 一个简单的比喻: 想象你在一座大雾弥漫的山里(…

Notepad++使用技巧1

1.打开官方参考代码经常看到下图这种行尾很多空格的代码&#xff0c;一点都不合符华为的书写规范&#xff0c;阅读起来容易让人烦躁不安。初学者建议看看华为的代码书写规范&#xff0c;你将少走很多弯路&#xff0c;终生受益。2.快速去掉行尾很多空格方法点击顶部菜单栏“宏”…

AIoT云边协同方式

随着物联网&#xff08;IoT&#xff09;与人工智能&#xff08;AI&#xff09;的深度融合&#xff0c;AIoT&#xff08;人工智能物联网&#xff09;作为一种新兴技术范式&#xff0c;正在推动智能设备与产业的快速发展。AIoT通过云边协同的方式&#xff0c;将边缘侧的IoT设备、…

MIT 6.5840 (Spring, 2024) 通关指南——Lab 1: MapReduce

MIT 6.5840 (Spring, 2024) – Lab 1: MapReduce &#x1f468;‍&#x1f4bb; Charles &#x1f517; 实验手册&#xff1a; 6.5840 Lab 1: MapReduce &#x1f4c3; MapReduce 论文原文&#xff1a; mapreduce-osdi04.pdf ✍️ 本系列前文&#xff1a; MIT 6.5840 (Spring, …

吴恩达机器学习作业五:神经网络正向传播

数据集在作业一正向传播正向传播&#xff08;Forward Propagation&#xff09;是神经网络计算过程中的核心步骤&#xff0c;指的是将输入数据通过神经网络的各层依次传递&#xff0c;最终得到输出结果的过程。核心原理在神经网络中&#xff0c;信息从输入层流入&#xff0c;经过…

网络编程(4)

【0】复习 sockfdsocket(); //指定网络信息 bind(); listen(); //创建表 fd_set rfds,tempfds; FD_ZERO(); FD_SET(sockfd); max sockfd while(1) {tempfdsrfds;select(max1,&tempfds)if(FD_ISSET(scokfd,&tempfds)){acceptfdaccept();FD_SET(acceptfd,&rfds);if(m…

Windows系统提示“找不到文件‘javaw‘”

1. Java 未安装或安装不完整javaw.exe 是 Java 运行环境&#xff08;JRE&#xff09;的核心文件&#xff0c;用于运行 Java 程序&#xff08;如.jar 文件&#xff09;。如果你的电脑没有安装 Java&#xff0c;或安装过程中 javaw.exe 被误删&#xff0c;系统就会找不到它。2. J…

【PCIE系列】1---PCIE系统拓扑结构分析

架构由点对点链路&#xff08;Links&#xff09;组成&#xff0c;用于互连组成系统的一系列组件。下图展示了一个示例拓扑结构。该图描述了一个有层次的体系架构实例&#xff0c;其包含根复合体&#xff08;Root Complex, RC&#xff09;、多个端点&#xff08;I/O设备&#xf…

SpringBoot防止重复提交(2)

例如&#xff1a;多次点击提现按钮问题描述&#xff1a;在提现操作中&#xff0c;用户可能会多次点击提现按钮&#xff0c;导致多个相同的请求发送到服务器&#xff0c;从而引发重复提现的问题。为了解决这一问题&#xff0c;必须保证每个提现请求只能执行一次&#xff0c;防止…

mysql zip包安装步骤

下载地址 windows MSI Install 安装包程序。 这里下载zip包&#xff0c;执行安装过程 确认my.ini 配置的路径&#xff0c;创建mysql数据服务的data目录管理员身份cmd 进入bin目录&#xff0c;开始初始化服务 mysqld --initialize-insecure --usermysql mysqld -install#启动…

Python 的 argparse 模块中,add_argument 方法的 nargs 参数

在 Python 的 argparse 模块中&#xff0c;add_argument 方法的 nargs 参数用于指定命令行参数可以接受的参数数量。你提到的 nargs* 和 nargs 是两种常见设置&#xff0c;它们分别表示不同的参数数量要求。以下是两者的详细区别和含义&#xff1a;1. nargs*: 接受零个或多个参…

嵌入式Linux LED驱动开发

嵌入式Linux LED驱动开发 一、LED驱动概述 本笔记基于IMX6ULL处理器的LED驱动开发&#xff0c;详细介绍了字符设备驱动开发的基本流程。该驱动实现了对LED的基本控制功能&#xff0c;通过字符设备接口供用户空间程序调用。 二、LED驱动核心概念 1. 寄存器地址定义 本驱动涉…

Excel Word Pdf 格式转换

引入aspose包手动更新本地mvn仓库mvn install:install-file -DfileC:\aspose-cells-22.9.jar -DgroupIdaspose -DartifactIdaspose-cells -Dversion22.9 -Dpackagingjar mvn install:install-file -DfileC:\aspose-pdf-22.9.jar -DgroupIdaspose -DartifactIdaspose-pdf -Dvers…

变频器实习DAY40 调整测试零伺服PI LDO

目录变频器实习DAY40一、工作内容1.1 调整测试零伺服PI二、学习内容2.1 LDOLDO的核心工作原理——“采样-比较-调整”闭环控制LDO的关键参数——选型核心依据LDO与其他稳压器的选型对比附学习参考网址欢迎大家有问题评论交流 (* ^ ω ^)变频器实习DAY40 一、工作内容 1.1 调整…