序列(Sequence)
除集合之外, Kotlin 还提供了另一种类型 – 序列(sequence) (Sequence<T>
). 与集合(Collection)不同, 序列并不包含元素, 而是在迭代时生成元素. 序列提供的函数和 Iterable
一样, 但对多步骤的集合处理提供另一种实现方式.
当对 Iterable
的处理包含多个步骤时, 会以及早计算(eager)模式执行: 每个处理步骤都会执行完毕, 并返回它的结果 – 也就是一个中间集合. 然后再对这个中间集合执行下一个步骤. 与此不同, 对序列的多步骤处理会尽量以延迟计算(lazy)模式执行: 只有在整个处理链的结果真正被使用到时, 才会执行相应的计算处理.
操作的执行顺序也不同: Sequence
对每个元素执行所有的处理步骤. 而 Iterable
会对整个集合执行单个处理步骤, 然后再对结果集合执行下一个处理步骤.
通过这种方式, 序列可以避免生成各个处理步骤的中间结果, 因此能够提高集合多步骤处理的整体性能. 然而, 序列的延迟计算(lazy)模式会增加一些开销, 在处理小集合, 或进行简单计算时, 这些开销可能会比较显著. 因此, 应该同时考虑使用 Sequence
和 Iterable
, 根据你的具体情况决定哪一个比较好.
创建序列
通过指定的元素创建
要创建序列, 可以使用 sequenceOf()
函数, 通过函数参数指定序列中的元素.
通过 Iterable 创建
如果你已经有了一个 Iterable
对象 (比如 List
或 Set
), 你可以调用它的 asSequence()
. 函数来创建序列.
通过函数创建
创建序列的另一种方式是, 通过一个函数来计算序列中的元素. 调用 generateSequence()
函数, 把序列元素的计算函数作为它的参数, 这样就可以通过函数来创建序列. 这个函数有一个可选的参数, 你可以明确指定第一个元素的值, 也可以指定一个函数来计算第一个元素的值. 当序列元素的计算函数返回 null
时, 序列的生成过程会停止. 所以, 下面示例中的序列是无限的.
如果想要使用 generateSequence()
函数创建有限的序列, 那么你的序列元素的计算函数应该在生成最后一个元素之后返回 null
.
通过数据块(chunk)创建
最后, 还有 sequence()
函数, 可以逐个生成序列元素, 或者通过任意大小的数据块来生成元素. 这个函数的参数是一个 lambda 表达式, 其中包括对 yield()
函数和 yieldAll()
函数的调用. 这些函数会将元素返回给序列的使用者, 然后暂停 sequence()
函数的执行, 直到序列使用者请求下一个元素. yield()
的参数是单个元素; yieldAll()
的参数可以是一个 Iterable
对象, 或一个 Iterator
, 或另一个 Sequence
. yieldAll()
函数的 Sequence
参数可以是无限的. 但是, 这样的调用必须出现在序列的最末尾: 否则, 在这之后的所有序列元素都不会被执行到.
序列的操作
根据对数据状态的要求不同, 序列的操作可以分为以下两类:
无状态(Stateless) 操作, 不需要保存状态信息, 对每个元素的处理都是独立的, 比如,
map()
或filter()
. 无状态操作本身可以使用固定数量的少量状态数据来处理一个元素, 比如,take()
或drop()
.有状态(Stateful) 操作, 需要大量的状态信息, 通常正比于序列内的元素数量.
如果序列的一个操作返回另一个序列, 结果序列的内容是延迟计算的, 我们称这个操作为 中间(intermediate) 操作. 相反, 如果一个操作不返回新的序列, 那么称为 终止(terminal) 操作. 终止操作的例子, 比如 toList()
或 sum()
. 只有执行终止操作后, 才能取得序列中的元素.
序列元素可以多次遍历; 序列的某些实现类可能造成限制, 使得它只能遍历一次. 这样的限制会在这些序列的文档中明确说明.
序列处理的示例
下面我们通过一个示例来看看 Iterable
和 Sequence
区别.
使用 Iterable
假设你有很多单词. 下面的代码会过滤长度超过 3 个字母的单词, 然后打印前 4 个这种单词的长度.
运行这段代码时, 你可以看到 filter()
和 map()
函数的执行顺序与它们在代码中出现的顺序相同. 首先, 你会看到对所有元素输出 filter:
, 然后对过滤之后剩余的元素输出 length:
, 然后是最后两行代码的输出.
下图是 list 各处理步骤的具体执行过程:
使用序列
下面我们用序列来实现同样的处理:
这段代码的输出显示, filter()
和 map()
函数在创建最终的结果 list 时才被执行. 因此, 你首先看到的输出是 "Lengths of.."
, 然后序列的处理才会开始. 注意, 对于过滤之后剩余的元素, 会在过滤下一个元素之前执行 map 操作. 当结果的元素数量到达 4 时, 处理将会停止, 因为这是 take(4)
能够返回的最大元素数量.
序列的处理过程如下:
在这个示例中, 序列处理执行了 18 步, 而使用 list 时则需要 23 步.