1. 概述

Kotlin 引入了 Sequence(序列)作为处理集合数据的一种高效方式。它的设计思想与 Java 的 Stream 类似,但在底层实现和使用场景上存在关键差异。本文将深入浅出地讲解 Sequence 的核心机制、适用场景以及常见用法,帮助你在实际开发中合理选择使用 List 还是 Sequence,避免踩坑。

✅ 正确理解 Sequence 的懒加载特性,能显著提升大数据量下的处理性能
❌ 盲目在小数据集上使用 Sequence,反而可能带来额外开销

2. 理解 Sequence

Sequence<T> 是一个接口,表示一个可惰性计算的元素序列。它支持两类操作:

  • 中间操作(Intermediate Operations):如 mapfilterflatMap 等,返回新的 Sequence,不立即执行
  • 终端操作(Terminal Operations):如 toListcountfindfirst 等,触发整个链式操作的执行

⚠️ 核心特点:懒加载(Lazy Evaluation)

当你对一个 Sequence 进行多个中间操作时,不会像普通集合那样每一步都生成一个中间集合。相反,这些操作会被“记录”下来,直到遇到终端操作才真正开始逐个元素处理。

举个例子:

(1..1000000)
    .asSequence()
    .filter { it % 2 == 0 }
    .map { it * 2 }
    .count { it > 1000 }

上述代码在整个流程中 不会创建任何中间 List,而是对每个元素依次判断、转换、计数,内存占用极低。

📌 适用场景:

  • 处理大型集合(成千上万条数据)
  • 需要链式多步处理且希望减少中间对象创建
  • 可能提前终止的操作(如 findfirst

📌 不推荐场景:

  • 小数据量(< 1000)
  • 操作简单或仅一步处理
  • 对性能要求不高

因为 Sequence 存在一定的调用开销(如闭包、迭代器封装),小数据量下反而不如直接使用 List 高效。

3. 创建 Sequence

3.1 通过元素创建

使用 sequenceOf() 快速构建一个有限序列:

val seqOfElements = sequenceOf("first", "second", "third")

3.2 通过函数创建(无限序列)

使用 generateSequence() 可以创建无限序列,适合生成时间戳、ID 流等:

val seqFromFunction = generateSequence(Instant.now()) { it.plusSeconds(1) }
// 示例输出:[2025-04-05T10:00:00Z, 2025-04-05T10:00:01Z, ...]

⚠️ 注意:无限序列必须配合终端操作使用(如 take(n)),否则会无限执行。

3.3 通过代码块生成(yield/yieldAll)

使用 sequence { } 构建器可以灵活控制元素产出,支持 yield()yieldAll()

val seqFromChunks = sequence {
    yield(1)
    yieldAll((2..5).toList()) // 批量添加 2,3,4,5
}

✅ 输出结果为:[1, 2, 3, 4, 5]

📌 最佳实践:

  • 若包含无限生成逻辑(如 while(true)),应放在最后
  • 使用 yieldAll 可提高批量添加效率

3.4 从集合转换

已有 Iterable(如 ListSetRange)可通过 asSequence() 转为 Sequence:

val seqFromIterable = (1..10).asSequence()

这是最常见的用法之一,尤其在需要链式处理大列表时。

4. 懒加载 vs 立即执行

我们通过对比两种写法来直观感受差异。

❌ 方式一:非 Sequence(立即执行)

val withoutSequence = (1..10).filter { it % 2 == 1 }.map { it * 2 }

执行过程如下:

val list = (0..10)
assert(list is IntRange)

val filtered = list.filter { it % 2 == 1 }  // ✅ 生成新 List [1,3,5,7,9]
assert(filtered is List<Int>)

val mapped = filtered.map { it * 2 }        // ✅ 再生成新 List [2,6,10,14,18]
assert(mapped is List<Int>)
assert(mapped.size == 5)

每一步都创建了中间集合,内存和 GC 压力较大。

✅ 方式二:使用 Sequence(懒加载)

val withSequence = (1..10).asSequence().filter { it % 2 == 1 }.map { it * 2 }.toList()

执行逻辑:

val sequence = (0..10).asSequence()
assert(sequence is Sequence)

val filtered = sequence.filter { it % 2 == 1 }
assert(filtered is Sequence)  // ❌ 不是 List,仍是 Sequence

val mapped = filtered.map { it * 2 }
assert(mapped is Sequence)

val list = mapped.toList()   // ✅ 只有到这里才真正执行并生成 List
assert(list is List<Int>)
assert(list.size == 5)

整个过程中没有中间集合,所有操作延迟到 toList() 才触发。

📌 关键点:

  • 所有中间操作都是“声明式”的,不执行
  • 终端操作才是“触发器”
  • 常见终端操作包括:toList()toSet()count()first()find()forEach()

⚠️ 易错点:Kotlin 的 Collection 扩展了和 Sequence 同名的方法(如 filtermap),导致初学者容易混淆:

// 这是 List 的扩展方法,立即执行
list.filter { ... }.map { ... }

// 这是 Sequence,懒加载
list.asSequence().filter { ... }.map { ... }.toList()

一定要注意是否调用了 asSequence(),否则你以为用了懒加载,其实还是 eager 模式。

5. 总结

Sequence 是 Kotlin 提供的一种强大的惰性集合处理工具,特别适合:

  • 大数据量的链式处理
  • 需要节省内存和提升性能的场景
  • 可能提前结束的操作(如查找第一个匹配项)

但也要警惕其“银弹”陷阱——小数据量下使用 Sequence 反而更慢

📌 使用建议:

  • 数据量 > 1000 条?优先考虑 Sequence
  • 操作步骤多且复杂?Sequence 更优
  • 仅一步操作或数据极少?直接用 List 即可

所有示例代码已托管至 GitHub:https://github.com/Baeldung/kotlin-tutorials/tree/master/core-kotlin-modules/core-kotlin


原始标题:Sequences in Kotlin