腾讯视频链接:
v.qq.com/x/page/d093…
Bilibili 视频链接:
www.bilibili.com/video/av977…
立即执行和延迟执行的区别在于每次对集合进行转换时,这个操作会在何时真正执行。
Collection(也称集合) 是在每次操作时立即执行的,执行结果会存储到一个新的集合中。作用于 Collection 的转换操作是内联函数。例如,map 的实现方式,可以看到它是一个创建了新 ArrayList 的内联函数:
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 等) 则不会。
Sequence 是不会保留对集合项目的引用的。它基于原始集合的迭代器 (iterator) 创建,并且保留要执行的所有中间操作的引用。
与在 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 等一系列操作,在使用它们之前,您得确保了解这些操作。
Collections
Sequences
使用 Sequence 时不会去创建中间集合,由于项目会被逐个执行,map 操作只会作用到部分输入上。
转换的顺序
无论您使用 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 开发的相关资料