Kotlin Vocabulary | Collection 和 Sequence

在不少场景中咱们会使用到集合,Kotlin 标准库 (Kotlin Standard Library) 中提供了很是多出色的关于集合的实用函数。其中,Kotlin 提供了基于不一样执行方式的两种集合类型: 当即执行 (eagerly) 的 Collection 类型, 延迟执行 (lazily) 的 Sequence 类型。本篇文章将向您介绍二者的区别,并向您介绍这两种类型分别该在哪一种状况下使用,以及它们的性能表现。

腾讯视频连接:html

v.qq.com/x/page/d093…android

Bilibili 视频连接:git

www.bilibili.com/video/av977…github

Collection 和 Sequence 的对比

当即执行和延迟执行的区别在于每次对集合进行转换时,这个操做会在什么时候真正执行。api

Collection(也称集合) 是在每次操做时当即执行的,执行结果会存储到一个新的集合中。做用于 Collection 的转换操做是内联函数。例如,map 的实现方式,能够看到它是一个建立了新 ArrayList 的内联函数:bash

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
  return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
复制代码

Sequence (也称序列) 是延迟执行的,它有两种类型: 中间操做 (intermediate) 和末端操做(terminal)。中间操做不会当即执行,它们只是被存储起来,仅当末端操做被调用时,才会按照顺序在每一个元素上执行中间操做,而后执行末端操做。中间操做 (好比 map、distinct、groupBy 等) 会返回另外一个Sequence,而末端操做 (好比 first、toList、count 等) 则不会。jvm

Sequence 是不会保留对集合项目的引用的。它基于原始集合的迭代器 (iterator) 建立,而且保留要执行的全部中间操做的引用。ide

与在 Collection 中执行转换操做不一样,Sequence 执行的中间转换不是内联函数,由于内联函数没法存储,而 Sequence 须要存储它们。咱们能够经过下列代码看到像 map 这样的中间操做是如何实现的,能够看到转换函数会存储在一个新的 Sequence 实例中:函数

public fun <T, R> Sequence<T>.map(transform: (T) -> R): Sequence<R>{      
   return TransformingSequence(this, transform)
}
复制代码

例如 first 这样的末端操做,会对 Sequence 中的元素进行遍历,直到预置条件匹配为止。性能

public inline fun <T> Sequence<T>.first(predicate: (T) -> Boolean): T {
   for (element in this) if (predicate(element)) return element
   throw NoSuchElementException(“Sequence contains no element matching the predicate.”)
}
复制代码

若是观察 TransformingSequence 这样的类型是如何实现的,咱们会发如今迭代器上调用 next 时,转换存储操做也一并被应用。

internal class TransformingIndexedSequence<T, R> 
constructor(private val sequence: Sequence<T>, private val transformer: (Int, T) -> R) : Sequence<R> {
override fun iterator(): Iterator<R> = object : Iterator<R> {
   …
   override fun next(): R {
     return transformer(checkIndexOverflow(index++), iterator.next())
   }
   …
}
复制代码

不管您使用 Collection 仍是 Sequence,Kotlin 标准库都提供了相似于 find、filter、groupBy 等一系列操做,在使用它们以前,您得确保了解这些操做

Collection 和 Sequence 如何选择

假设咱们有一个列表,存储了许多不一样形状的对象,咱们但愿将列表中形状的颜色变成黄色,而后获取列表中的第一个正方形。

运行 Collection 和 Sequence 代码
咱们来看一下针对 Collection 和 Sequence 的各个操做是如何执行以及什么时候执行的。

Collections

  • 调用 map 时 —— 一个新的 ArrayList 会被建立。咱们遍历了初始 Collection 中全部项目,复制原始的对象,而后更改它的颜色,再将其添加到新的列表中;
  • 调用 first 时 —— 遍历每个项目,直到找到第一个正方形。

Sequences

  • asSequence —— 基于原始集合的迭代器建立一个 Sequence;
  • 调用 map 时 —— Sequence 会将转换操做的信息存储到一个列表中,该列表只会存储要执行的操做,并不会执行这些操做;
  • 调用 first 时 —— 这是一个末端操做,因此会将中间操做做用到集合中的每一个元素。咱们遍历初始集合,对每一个元素执行 map 操做,而后继续执行 first 操做,当遍历到第二个元素时,发现它符合咱们的要求,因此就无需在剩余的元素中进行 map 操做了。

使用 Sequence 时不会去建立中间集合,因为项目会被逐个执行,map 操做只会做用到部分输入上。

Collection 和 Sequence 的对比 — 当即处理和延迟处理间的对比

性能

转换的顺序

不管您使用 Collection 仍是 Sequence,转换的顺序都很重要。在上面的例子中,first 不须要先在 map 以后进行操做,由于 first 不须要 map 操做的结果就可以执行。若是咱们颠倒业务逻辑的顺序,先把 first 做用到 Collection 上,再对结果执行转换,那么咱们只会建立一个新的对象 —— 一个黄色的正方形。当使用 Sequence 时,会避免建立两个新对象,而当使用 Collection 时则会避免建立整个列表。

转换顺序的重要性 — 避免无用操做
由于末端操做能够提早对任务进行处理,而中间操做会延迟进行处理,因此在某些状况下,相比于 Collection,Sequence 能够避免一些无用操做。使用时,请确保检查了转换顺序以及它们的依赖关系。

内联和大数据集所带来的影响

Collection 的操做使用了内联函数,因此处理所用到的字节码以及传递给它的 lambda 字节码都会进行内联操做。而 Sequence 不使用内联函数,所以,它会为每一个操做建立新的 Function 对象。

另外,Collection 会为每一个转换操做建立一个新的列表,而 Sequence 仅仅是保留对转换函数的引用。

当对数据量小的 Collection 执行 1 到 2 个操做时,上面所说的差别并不会带来什么样的影响,因此这种状况下使用Collection 是没问题的。而当列表数据很大时,中间集合的建立会很消耗资源,这种状况下就应该使用 Sequence

不幸的是,我不知道有什么样的基准测试可以帮助咱们更好地探索出具体不一样大小的集合或者操做链才会对 Collection 和 Sequence 产生影响。

综上所述,Collection 会当即执行对数据的操做,而 Sequence 则是延迟执行。根据要处理的数据量大小,选择最合适的一个: 数据量小,则使用 Collection,数据量大,则使用 Sequence,另外,需注意操做顺序带来的影响。

点击这里了解更多关于用 Kotlin 进行 Android 开发的相关资料

相关文章
相关标签/搜索