这两个接口都只定义了一个方法
public operator fun iterator(): Iterator<T>
文档说Sequence
是懒惰的。但是Iterable
也不是懒惰(除非由Collection
支持)?
答案 0 :(得分:113)
关键区别在于Iterable<T>
和Sequence<T>
的语义和stdlib扩展函数的实现。
对于Sequence<T>
,扩展函数尽可能延迟执行,类似于Java Streams 中间操作。例如,Sequence<T>.map { ... }
会返回另一个Sequence<R>
,并且在调用toList
或fold
之类的终端操作之前,实际上并不处理这些项目。
考虑以下代码:
val seq = sequenceOf(1, 2)
val seqMapped: Sequence<Int> = seq.map { print("$it "); it * it } // intermediate
print("before sum ")
val sum = seqMapped.sum() // terminal
打印:
before sum 1 2
当您希望尽可能减少终端操作中的工作时, Sequence<T>
旨在用于延迟使用和高效流水线操作,与Java Streams相同。然而,懒惰引入了一些开销,这对于较小集合的常见简单转换是不合需要的,并且使得它们的性能较差。
一般情况下,没有好的方法可以确定何时需要它,因此在Kotlin中,stdlib将laziness明确化并提取到Sequence<T>
接口,以避免在所有Iterable
上使用它默认值。
对于Iterable<T>
,相反,带有中间操作语义的扩展函数急切地工作,立即处理项目并返回另一个Iterable
。例如,Iterable<T>.map { ... }
会返回List<R>
,其中包含映射结果。
Iterable的等效代码:
val lst = listOf(1, 2)
val lstMapped: List<Int> = lst.map { print("$it "); it * it }
print("before sum ")
val sum = lstMapped.sum()
打印出来:
1 2 before sum
如上所述,默认情况下Iterable<T>
是非延迟的,这个解决方案很好地表现出来:在大多数情况下它具有良好的locality of reference,因此利用了CPU缓存,预测,预取等等。甚至多次复制一个集合仍然可以很好地工作,并且在小集合的简单情况下表现更好。
如果您需要对评估管道进行更多控制,则可以使用Iterable<T>.asSequence()
函数显式转换为延迟序列。
答案 1 :(得分:36)
完成热键的回答:
重要的是要注意Sequence和Iterable如何在整个元素中迭代:
序列示例:
list.asSequence()
.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
记录结果:
过滤器 - 地图 - 每个;过滤器 - 地图 - 每个
可重复的例子:
list.filter { field ->
Log.d("Filter", "filter")
field.value > 0
}.map {
Log.d("Map", "Map")
}.forEach {
Log.d("Each", "Each")
}
filter - filter - Map - Map - Each - Each
答案 2 :(得分:0)
Iterable
映射到java.lang.Iterable
,由常用集合(例如List或 组。评估这些上的集合扩展功能 急切地,这意味着它们都会立即处理其中的所有元素 他们的输入,并返回一个包含结果的新集合。这是一个使用收集函数获取 列表中年龄至少21岁的前五个人的名字:
JVM
目标平台:在kotlin v。1.3.61上运行的JVM首先,检查年龄 为列表中的每个人完成的操作,结果放入 全新清单。然后,对每个人的姓名进行映射 仍在过滤器运算符之后但仍留在其中的人 另一个新列表(现在是
val people: List<Person> = getPeople() val allowedEntrance = people .filter { it.age >= 21 } .map { it.name } .take(5)
)。最后,有一个 创建的最后一个新列表包含该列表的前五个元素 以前的列表。相反,Sequence是Kotlin中的一个新概念,代表懒惰 评估值的集合。相同的集合扩展是 可用于
List<String>
界面,但这些会立即返回 表示日期的已处理状态的序列实例,但是 而不实际处理任何元素。要开始处理,Sequence
必须由终端操作员终止,这是 基本上是对序列的要求,以实现其数据 以某种具体形式表示。示例包括Sequence
,toList
, 和toSet
,仅举几例。当这些被调用时,只有 最少需要的元素数量将被处理以生成 要求的结果。将现有集合转换为序列很漂亮 直截了当,您只需要使用
sum
扩展名。如 上面提到的,还需要添加一个终端运算符,否则 该序列将永远不会进行任何处理(再次,懒惰!)。asSequence
目标平台:在kotlin v。1.3.61上运行的JVM在这种情况下, 序列中的每个人物实例都会被检查年龄,如果 他们通过了,提取了他们的名字,然后将其添加到 结果列表。对原始列表中的每个人重复此操作 直到找到五个人。至此,toList函数 返回一个列表,
val people: List<Person> = getPeople() val allowedEntrance = people.asSequence() .filter { it.age >= 21 } .map { it.name } .take(5) .toList()
中的其他人不是 已处理。序列还具有一些其他功能:它可以包含 无限数量的项目。从这个角度来看,这很有意义 运算符以他们的方式工作-无限运算符 如果急切地工作,顺序就永远不会返回。
例如,这是一个序列,它将产生 2根据其终端操作员的要求(忽略此事实 会很快溢出):
Sequence
您可以找到更多here。
答案 3 :(得分:0)
Iterable 对于大多数用例来说已经足够好了,对它们执行迭代的方式由于空间局部性,它与缓存一起工作得很好。 但它们的问题是整个集合必须通过第一个中间操作,然后才能移动到第二个等等。
sequence
中,每个项目在下一个之前通过完整的管道处理。此属性可能会影响代码的性能,尤其是在迭代大型数据集时。因此,如果您的终端操作很可能提前终止,那么 sequence
应该是首选,因为您可以通过不执行不必要的操作来节省成本。例如
sequence.filter { getFilterPredicate() }
.map { getTransformation() }
.first { getSelector() }
在上述情况下,如果第一项满足 filter
谓词并且在 map
转换后满足选择条件,则仅调用 filter
、map
和 first
一次。
如果是可迭代的整个集合,必须先过滤然后映射,然后开始第一次选择