1. 概述
在 Kotlin 中,我们经常使用集合操作来处理数据。虽然 Collection
和 Sequence
的 API 看起来非常相似,但它们在执行时机上有本质区别。
本文将通过代码示例说明以下几点:
Collection
是立即执行(Eager Evaluation)的Sequence
是延迟执行(Lazy Evaluation)的- 在不同场景下应如何选择使用哪个 API
2. 实现原理对比
2.1. 立即执行(Eager Evaluation)
✅ Collection
的操作是立即执行的。每个操作都会对整个集合中的每个元素执行一次,并将结果保存到一个新的集合中:
(1..100)
.map { it * it } // 创建一个新列表 [1,4,9,16...10000]
.filter { it % 2 == 0 } // 再创建一个新列表 [4,16,...10000]
.first { it > 50 } // 找到第一个大于 50 的元素
例如 filter()
方法的实现如下:
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
像 map()
、filter()
、flatMap()
、reduce()
、fold()
等方法,都是立即执行的,每次都会生成一个中间集合。
2.2. 延迟执行(Lazy Evaluation)
✅ Sequence
的操作是延迟执行的。它不会在每一步都创建中间集合,而是按需处理每个元素,逐个传递下去:
(1..100).asSequence()
.map { it * it }
.filter { it % 2 == 0 }
.first { it > 50 }
在这个例子中,map()
会先对每个数字求平方,然后 filter()
会判断是否为偶数,只有通过过滤的元素才会继续传给 first()
。
⚠️ 举个例子:1, 9, 25, 49
这些数字虽然通过了 map()
,但被 filter()
拒绝了,所以不会进入 first()
,也不会被处理。
我们只需要遍历前 8 个数字 [1, 2, 3, 4, 5, 6, 7, 8]
就能拿到结果,避免了 92 次多余的 map()
和 filter()
调用。
为了更清晰地看到执行过程,我们给每个步骤加日志:
(1..100).asSequence()
.map {
print("\nTaking: $it")
(it * it).also { print(" -> Square: $it") }
}
.filter { (it.also { print(" -> Even-Filtering: $it") } % 2 == 0) }
.first { it.also { print(" -> Comparing $it to 50") } > 50 }
.also { println(" -> Found: $it") }
输出如下:
Taking: 1 -> Square: 1 -> Even-Filtering: 1
Taking: 2 -> Square: 4 -> Even-Filtering: 4 -> Comparing 4 to 50
Taking: 3 -> Square: 9 -> Even-Filtering: 9
Taking: 4 -> Square: 16 -> Even-Filtering: 16 -> Comparing 16 to 50
Taking: 5 -> Square: 25 -> Even-Filtering: 25
Taking: 6 -> Square: 36 -> Even-Filtering: 36 -> Comparing 36 to 50
Taking: 7 -> Square: 49 -> Even-Filtering: 49
Taking: 8 -> Square: 64 -> Even-Filtering: 64 -> Comparing 64 to 50 -> Found: 64
可以看到,每个元素在每一步中被逐一处理,直到找到满足条件的值为止。
2.3. Sequence 内部实现
Sequence<T>
的每个操作(如 map()
、filter()
)都有自己的实现类,如 TransformingSequence
、FilteringSequence
,它们不会创建新的 ArrayList<T>
。
以 filter()
为例:
public fun <T> Sequence<T>.filter(predicate: (T) -> Boolean): Sequence<T> {
return FilteringSequence(this, true, predicate)
}
再看 map()
的实现:
public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R> {
return TransformingSequence(this, transform)
}
TransformingSequence
的 iterator()
方法如下:
override fun iterator(): Iterator<R> = object : Iterator<R> {
val iterator = sequence.iterator()
override fun next(): R {
return transformer(iterator.next())
}
override fun hasNext(): Boolean {
return iterator.hasNext()
}
}
可以看到,Sequence
并不会立即处理所有元素,而是在每次调用 next()
时才进行变换,从而实现了延迟执行。
3. 函数式用法对比
3.1. List<T>
vs. Sequence<T>
- ✅ 返回
List<T>
的操作是Collection
,立即执行 - ✅ 返回
Sequence<T>
的操作是Sequence
,延迟执行
public fun <T> listOf(vararg elements: T): List<T>
public fun <T> sequenceOf(vararg elements: T): Sequence<T>
3.2. 使用 Collection 操作
Kotlin 标准库为 Iterable<T>
提供了丰富的扩展函数,比如:
listOf(1, 2, 3, 4, 5, 6)
.map { it * it } // [4, 16, 36]
.filter { it % 2 == 0 } // [4, 16, 36]
这些操作无需手动编写循环,就能实现数据转换。
常用操作包括:
map()
filter()
flatMap()
reduce()
fold()
更多操作请参考 Kotlin 官方文档
3.3. 使用 Sequence 操作
Sequence<T>
的操作分为两类:
- ✅ 中间操作(Intermediate operations):不会立即执行,只保存操作逻辑
- ✅ 终端操作(Terminal operations):如
toList()
、toSet()
、first()
,触发整个操作链的执行
示例:
val evens = sequenceOf(1, 2, 3, 4, 5)
.filter { it % 2 == 0 }
.map { it * it }
.toList() // [4, 16]
evens.onEach {
print(it)
}
⚠️ 如果没有 toList()
,则 onEach()
不会执行,因为 Sequence
必须有终端操作才能触发整个流程。
4. 性能对比
4.1. 大数据量场景
在处理大数据量时,Collection
的链式操作会频繁创建中间集合,带来显著性能开销。
✅ 推荐使用 Sequence
来优化复杂链式操作,尤其是在处理大型数据集时。
比如,Kotlin Lint 会提示你将 Collection
转换为 Sequence
:
4.2. 操作顺序影响性能
无论使用 Collection
还是 Sequence
,操作顺序对性能都有影响。
❌ 以下写法效率较低:
(1..100)
.onEach { println(it) } // 打印 100 次
.filter { it % 2 == 0 } // 再过滤 100 次
✅ 更高效的方式是先过滤,再打印:
(1..100)
.filter { it % 2 == 0 } // 先过滤
.onEach { println(it) } // 只打印 50 次
这样可以减少一半的打印操作,提升效率。
5. 总结
特性 | Collection | Sequence |
---|---|---|
执行方式 | 立即执行 | 延迟执行 |
中间集合 | 每次操作都生成新集合 | 按需处理,无中间集合 |
性能 | 小数据量表现良好 | 大数据量更高效 |
适用场景 | 简单操作、小数据集 | 复杂链式操作、大数据集 |
✅ 总结:
- ✅ 对于小数据集或简单操作,使用
Collection
更简洁直观 - ✅ 对于大数据集或复杂链式操作,使用
Sequence
更高效
所有示例代码可参考 GitHub。