输入:序列
为了解决上述性能瓶颈问题,Kotlin 提供了另一个名为 序列 的数据结构,它以惰性求值的方式处理集合中的数据。在你访问序列中的数据之前它们都不会被处理。序列非常适合表示事先不知道大小的集合,比如:从文件中读取行。
除了本章开始时学习的基本概念,sequence
还基于一个基本规则:它允许你在集合元素中执行多个中间操作,但是强制要求你有一个终结操作来获取序列的结果。因此,这使得序列处理无限大小元素的集合成为可能。
为了验证序列的惰性求值,可以在 SequenceExample.kt 中查看下面的代码片段:
fun main() {
val list = listOf(1, 2, 3)
// 1
list.asSequence().filter {
print("filter, ")
it > 0
}.map {
print("map, ")
}.forEach {
print("forEach, ")
}
}
这里的代码片段与之前 Iterable
的代码片段完全相同,只是使用 asSequence
将 List
转换为序列。
输出如下:
filter, map, forEach, filter, map, forEach, filter, map, forEach,
注意看在列表中调用的 asSequence
函数。创建序列有多种方式。最常用的场景是从一个 Iterable
类型的元素集合上调用 asSequence
函数来创建序列,比如一个 List
,Set
或者 Map
。
asSequence
是 Iterable 的一个扩展函数:
public fun <T> Iterable<T>.asSequence(): Sequence<T> {
return Sequence { this.iterator() }
}
注意,在上面的例子中,序列上的每个函数操作符如何在每次传递时在整个管道上迭代,在每个函数操作符里处理结果,然后将其传递给下一个函数操作符。
比如:filter
:
- 在序列上第一个执行的操作符。
- 打印 "filter, "。
- 计算元素是否大于零,并返回结果。
filter
操作符的结果将传递给下一个函数操作符 map
。此时,map
操作符的代码块中打印 "map, "。由于未处理任何内容,因此该项将传递给管道中的下一个操作符 forEach
。
现在,在 forEach
操作符中打印 “forEach, ”并且完成本次迭代。接下来在整个管道上处理序列的下一次迭代,以此类推,直到所有的迭代执行,结果正确的输出。
很明显,使用序列有助于避免不必要的临时分配开销,并可能显著提高处理复杂管道的性能,因为在处理时不需要计算整个项目集合。序列结构可以使你能够通过使用丰富流畅的 API 链式纯函数调用,轻松地对元素集合进行操作。
然而,惰性也会引入一些开销,这对于较小集合的常见简单转换来说是不可取的,并且会降低它们的性能。在大多数情况下仍然建议使用简单的 Iterables
。使用序列的好处仅在于当具有多个操作的元素的大集合或者无限集合时,特别是当你过滤元素时。
提示
注意: Java 8 和 Scala 都有和序列相似的「流」概念。Kotlin 选择使用序列作为新的类时为了避免在 Java 8 JVM 上的命名冲突并且能向后兼容老的 JVM。
了解如何在 Kotlin 中处理无限的元素集合,打开了使用无限元素的更多可能性的大门。其中一种可能性是构建 generator
函数。