1. 概述
Kotlin 引入了 Sequence(序列)作为处理集合数据的一种高效方式。它的设计思想与 Java 的 Stream 类似,但在底层实现和使用场景上存在关键差异。本文将深入浅出地讲解 Sequence 的核心机制、适用场景以及常见用法,帮助你在实际开发中合理选择使用 List
还是 Sequence
,避免踩坑。
✅ 正确理解 Sequence 的懒加载特性,能显著提升大数据量下的处理性能
❌ 盲目在小数据集上使用 Sequence,反而可能带来额外开销
2. 理解 Sequence
Sequence<T>
是一个接口,表示一个可惰性计算的元素序列。它支持两类操作:
- 中间操作(Intermediate Operations):如
map
、filter
、flatMap
等,返回新的Sequence
,不立即执行 - 终端操作(Terminal Operations):如
toList
、count
、find
、first
等,触发整个链式操作的执行
⚠️ 核心特点:懒加载(Lazy Evaluation)
当你对一个 Sequence 进行多个中间操作时,不会像普通集合那样每一步都生成一个中间集合。相反,这些操作会被“记录”下来,直到遇到终端操作才真正开始逐个元素处理。
举个例子:
(1..1000000)
.asSequence()
.filter { it % 2 == 0 }
.map { it * 2 }
.count { it > 1000 }
上述代码在整个流程中 不会创建任何中间 List,而是对每个元素依次判断、转换、计数,内存占用极低。
📌 适用场景:
- 处理大型集合(成千上万条数据)
- 需要链式多步处理且希望减少中间对象创建
- 可能提前终止的操作(如
find
、first
)
📌 不推荐场景:
- 小数据量(< 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
(如 List
、Set
、Range
)可通过 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
同名的方法(如 filter
、map
),导致初学者容易混淆:
// 这是 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