Skip to content

Stream 优化

Stream 写起来很爽,但用错了也会变慢。

这一节不讲语法,讲性能。什么样的写法快,什么样的写法慢,以及为什么。

减少中间操作

中间操作越少,管道越短,执行越快。

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

long count = numbers.stream()
    .filter(n -> n > 3)    // 操作 1
    .filter(n -> n % 2 == 0)  // 操作 2:可以合并到上面
    .count();

// ✅ 合并条件:少一次遍历
long count2 = numbers.stream()
    .filter(n -> n > 3 && n % 2 == 0)
    .count();

// ❌ 多次 map
List<String> result = numbers.stream()
    .map(n -> n * 2)          // 操作 1
    .map(Object::toString)    // 操作 2:可以合并
    .collect(Collectors.toList());

// ✅ 合并 map
List<String> result2 = numbers.stream()
    .map(n -> String.valueOf(n * 2))
    .collect(Collectors.toList());

善用短路操作

短路操作让 Stream 不必处理全部数据就返回结果:

java
// ❌ 不用短路:处理了全部数据
boolean allMatch = list.stream()
    .filter(n -> n > 0)  // 所有数据都过了一遍 filter
    .allMatch(n -> n < 100);

// ✅ 用短路:找到第一个不匹配的立即返回
boolean allMatch2 = list.stream()
    .allMatch(n -> n > 0 && n < 100);

takeWhile / dropWhile(JDK 9+)

java
// takeWhile:取元素直到条件不满足就停止
List<Integer> result = Stream.of(1, 2, 3, 4, 1, 2)
    .takeWhile(n -> n < 4)
    .toList();
// [1, 2, 3](遇到 4 就停了,不再处理后面的 1, 2)

// dropWhile:跳过元素直到条件不满足
List<Integer> result2 = Stream.of(1, 2, 3, 4, 1, 2)
    .dropWhile(n -> n < 4)
    .toList();
// [4, 1, 2](跳过 1, 2, 3,遇到 4 之后全部保留)

避免装箱开销

处理基本类型时,用专用流类型:

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

// ❌ 自动装箱:Integer 对象
long start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
    int sum = numbers.stream()
        .reduce(0, (a, b) -> a + b);  // 每次都装箱/拆箱
}

// ✅ 避免装箱:IntStream
start = System.currentTimeMillis();
for (int i = 0; i < 100_000; i++) {
    int sum = numbers.stream()
        .mapToInt(Integer::intValue)  // 转成 int[]
        .sum();
}
类型专用流避免装箱
intIntStream
longLongStream
doubleDoubleStream
其他Stream<Integer>

filter 后置

把能过滤的数据尽量提前过滤,减少后续操作的数据量:

java
// ❌ filter 靠后:先处理大量数据再过滤
list.stream()
    .map(this::expensiveTransform)
    .filter(result -> result.isValid())
    .count();

// ✅ filter 靠前:先缩小数据量
list.stream()
    .filter(this::isPromising)       // 先过滤
    .map(this::expensiveTransform)   // 只处理少量数据
    .filter(result -> result.isValid())
    .count();

使用正确的数据结构

有些数据结构天然适合并行操作:

java
// ArrayList:适合并行(可快速分割)
List<Integer> arrayList = new ArrayList<>(Arrays.asList(1, 2, 3));
arrayList.parallelStream()...  // 好

// LinkedList:不适合并行(分割成本高)
List<Integer> linkedList = new LinkedList<>(Arrays.asList(1, 2, 3));
linkedList.parallelStream()...  // 差,拆分 LinkedList 代价高

// HashSet:适合并行
Set<Integer> hashSet = new HashSet<>(Arrays.asList(1, 2, 3));
hashSet.parallelStream()...  // 好

// Stream.of/Arrays.stream:适合并行
IntStream.range(0, 1_000_000)...  // 好

惰性求值的好处

中间操作的惰性带来了优化的可能:

java
// 只取前 10 个,即使数据有 1000 万条
// Stream 只会处理够找到 10 个结果所需的数据
list.stream()
    .filter(n -> n > 0)
    .sorted()        // 这步不需要对全部数据排序
    .limit(10)
    .collect(Collectors.toList());

// 理解执行顺序
list.stream()
    .limit(10)       // ① 先 limit 限制数量
    .filter(n -> n > 0)  // ② 只处理前 10 个
    .collect(Collectors.toList());

并行 Stream 的条件

并行不是银弹。满足以下条件时并行才有效:

  1. 数据量大(> 1 万条)
  2. 操作是 CPU 密集或 IO 密集的
  3. 操作是无状态的
  4. 数据结构适合分割(ArrayList > 数组 > HashSet >> LinkedList)
java
// ✅ 并行有效:大数据量 + CPU 密集
IntStream.range(0, 10_000_000)
    .parallel()
    .filter(n -> isPrime(n))
    .count();

// ❌ 并行无效:小数据量
Arrays.asList(1, 2, 3)
    .parallelStream()
    .map(n -> n * 2)
    .collect(Collectors.toList());

// ❌ 并行无效:有状态
List<Integer> result = new ArrayList<>();
stream.parallelStream()
    .forEach(n -> result.add(n));  // 有共享可变状态

常用优化清单

优化项做法效果
合并 filterfilter(a && b)减少遍历次数
合并 mapmap(f.andThen(g))减少中间数组
filter 靠前先 filter 再 map减少处理量
用基本类型流IntStream vs Stream<Integer>避免装箱
用短路操作findFirst vs collect提前退出
选对数据结构ArrayList vs LinkedList并行效率
并行用对场景大数据量 + 无状态线性加速

记住:优化代码的第一步是测量。先跑个基准测试,确认慢在哪里,再针对性优化。

基于 VitePress 构建