跳至主要內容

序列入门

guodongAndroid大约 3 分钟

要紧跟本章的代码,请使用 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. 首先你创建了一个集合,在本例中,是数字 1、2 和 3 的列表。
  2. 然后,你在列表上执行 filter 操作符,在其中打印一条语句,显示你正在过滤值。你将过滤所有大于 0 的值。
  3. 接下来,你在列表上执行 map 操作符,在其中打印一条语句,显示你正在转换值,将数字转换为 String
  4. 最后,你在列表上调用 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。这种行为在其他操作符中是类似的,其中它们的实现在完成执行时返回一个完整的集合。

如果要向管道中添加更多操作符,则随着集合中元素数量的增加,性能将开始下降。这是因为每个操作符首先需要遍历集合中的所有元素,然后返回一个新的集合(同类)作为结果传递给下一个函数操作符,后者也会执行同样的操作。以下两个方面将受影响:

  1. 内存: 每个操作符都会返回新的集合。
  2. 性能: 每个操作符都会处理完整的集合。

这种情况实际上使得无法将 Collections API 用于无限的元素集合。Kotlin 语言发明人注意到了这个瓶颈问题并提出了一个解决方案。