序列入门
要紧跟本章的代码,请使用 IntelliJ 并点击 Open project 打开本章的项目,然后定位至 10-building-sequences-and-iteratorswith-yield/projects/starter,最后选择 sequences-and-iterators 项目。你将看到一些预定义的代码,这些代码将在本章中进行描述和解释。
让我们首先从迭代器开始。
迭代值
Kotlin 通过定义良好的 Collections API 以及许多可任你使用的函数运算符,提供了许多管理数据集合的方法。
然而,集合并不总是最有效的解决方案。在许多情况下,它们会导致性能降低,并且在对多个项目执行多个操作时会导致瓶颈。这主要是因为集合上的所有函数运算符都被及早(贪婪)执行:即在将结果传递给下一个运算符之前完全处理所有项目。
为了提供更多的见解,请查看 Collection
接口的签名:
public interface Collection<out E> : Iterable<E>
如你所见,Collection
接口集成自 Iterable
接口。Iterable<E>
在默认情况下是非惰性的,或者及早(贪婪)的。因此,所有集合都是及早(贪婪)的。
为了验证 Iterable 及早(贪婪)求值的本质,请查看以下代码,以下代码可以在 IteratorExample.kt 中获得:
fun main() {
// 1
val list = listOf(1, 2, 3)
// 2
list.filter {
print("filter, ")
it > 0
}.map { // 3
print("map, ")
it.toString()
}.forEach { // 4
print("forEach, ")
}
}
上述代码有些事情需要解释:
- 首先你创建了一个集合,在本例中,是数字 1、2 和 3 的列表。
- 然后,你在列表上执行
filter
操作符,在其中打印一条语句,显示你正在过滤值。你将过滤所有大于 0 的值。 - 接下来,你在列表上执行
map
操作符,在其中打印一条语句,显示你正在转换值,将数字转换为String
。 - 最后,你在列表上调用
forEach
操作符,它打印每个迭代的最终语句。
输出结果如下:
filter, filter, filter, map, map, map, forEach, forEach, forEach,
请注意,列表上的每个操作符如何在整个列表上迭代,以处理每个项的结果,最后将项传递给下一个函数操作符。
为了理解上述结果的过程,可以查看其中一个函数操作符 filter
的源码:
/**
* Returns a list containing only elements matching the given [predicate].
*/
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
filter
是一个从 filterTo
函数返回结果的扩展函数。继续深入了解 filterTo
函数的源码:
/**
* Appends all elements matching the given [predicate] to the given [destination].
*/
public inline fun <T, C : MutableCollection<in T>> Iterable<T>.filterTo(destination: C, predicate: (T) -> Boolean): C {
for (element in this) if (predicate(element)) destination.add(element)
return destination
}
从源码中可以显而易见,filterTo
在所有元素上执行一个简单的 for
循环,有效地检查 if
块中的条件,将元素附加到新的 ArrayList
中。当迭代并添加完所有元素,结果将返回给 filter
。这种行为在其他操作符中是类似的,其中它们的实现在完成执行时返回一个完整的集合。
如果要向管道中添加更多操作符,则随着集合中元素数量的增加,性能将开始下降。这是因为每个操作符首先需要遍历集合中的所有元素,然后返回一个新的集合(同类)作为结果传递给下一个函数操作符,后者也会执行同样的操作。以下两个方面将受影响:
- 内存: 每个操作符都会返回新的集合。
- 性能: 每个操作符都会处理完整的集合。
这种情况实际上使得无法将 Collections API 用于无限的元素集合。Kotlin 语言发明人注意到了这个瓶颈问题并提出了一个解决方案。