前言
随着StateFlow/SharedFlow
的稳定, Coroutine Flow
功能也越来越强大,那么让我们来看看实际开发中能用它来解决什么问题。在此之前还要介绍下Collections
、Sequence
、Channel
它们虽然是解决不同问题的事物,但是它们之间却有一些隐性联系。
Collections
简介
Collections
是Kotlin
标准库提供的一整套用于管理集合的工具,常见的集合类型有:
-
List
是一个有序集合,可通过索引(反映元素位置的整数)访问元素。元素可以在 list 中出现多次。列表的一个示例是一句话:有一组字、这些字的顺序很重要并且字可以重复。 -
Set
是唯一元素的集合。它反映了集合(set)的数学抽象:一组无重复的对象。一般来说 set 中元素的顺序并不重要。例如,字母表是字母的集合(set)。 -
Map
(或者字典)是一组键值对。键是唯一的,每个键都刚好映射到一个值。值可以重复。map 对于存储对象之间的逻辑连接非常有用,例如,员工的 ID 与员工的位置。
//list
val stringList = listOf("one", "two", "one")
//set
val stringSet = setOf("one", "two", "three")
//map
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
复制代码
操作
Kotlin
标准库提供了用于对集合执行操作的多种函数。这包括简单的操作,例如获取或添加元素,以及更复杂的操作,包括搜索、排序、过滤、转换等。
listOf(2, 1, 4, 7)
.map {
println("map $it")
it * 2
}
.filter {
println("filter $it")
it > 2
}
.take(2).forEach {
println("final $it-------")
}
log:
//排序之后
map 1
map 2
map 4
map 7
filter 2
filter 4
filter 8
filter 14
final 4-------
final 8-------
复制代码
以上每个处理步骤会创建额外的集合来保存过程中产生的中间结果:
//map
public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}
//filter
public inline fun <T> Iterable<T>.filter(predicate: (T) -> Boolean): List<T> {
return filterTo(ArrayList<T>(), predicate)
}
复制代码
这里最后只需要2个数据,但每个数据都进行了处理,造成了一定的浪费。怎么办呢?
Sequence
简介
Kotlin
标准库还包含另一种容器类型——序列(Sequence<T>
)。 序列提供与 Iterable
相同的函数,但使用另一种方法来进行多步骤集合处理。
Sequence
对每个元素逐个执行所有处理步骤,并且是惰性的。
操作
当我们将集合转换为sequence之后再进行操作:
listOf(2, 1, 4, 7)
.asSequence()
.map {
println("map $it")
it * 2
}
.filter {
println("filter $it")
it > 2
}
.take(2).forEach {
println("final $it-------")
}
输出:
map 1
filter 2
map 2
filter 4
final 4-------
map 4
filter 8
final 8-------
复制代码
可以看到是逐个执行所有操作,但性能提升也是相对。
惰性
序列操作分为两个过程:中间操作、末端操作。中间操作全部都是惰性操作:如果没有执行末端操作,中间操作都不会被执行。
| ----- 中间操作 ----- |
sequence.map { ... }.filter { ... }.forEach{}
|-末端操作-|
复制代码
Show code:
val list = listOf(1, 2, 3, 4)
list.asSequence().filter { println("first:$it");it > 2 }
list.asSequence().filter { println("second:$it");it > 2 }.toList()
print:
second:1
second:2
second:3
second:4
复制代码
可以看到不加toList()
的是不会有任何结果的,只有加上了toList()
才会执行print
操作,也就是为什么说中间操作是惰性的,在没有末端操作的时候,中间操作会被延期执行。
- 如果序列操作返回延迟生成的另一个序列,称为 中间序列。 否则该操作为 末端操作。
我们可以把这些操作符称之为末端操作符。 所以也可以说Sequence
是冷的?。cold or lazy
-
这些序列可避免生成中间步骤的结果,从而提高了整个集合处理链的性能。 但是,序列的延迟性质增加了一些开销,这些开销在处理较小的集合或进行更简单的计算时可能很重要。 因此,应该同时考虑使用
Sequence
与Iterable
,并确定在哪种情况更适合。 -
原理是所有操作封装进同一个
Iterator
,末端操作执行next
就跑起来了
关于性能分析是要看情况的:
Kotlin : Slow List and Lazy Sequence
[译]Kotlin中是应该使用序列(Sequences)还是集合(Lists)?
构建
以上都是由Iterable
对象 调用asSequence
进行转换的,那么还有其他构建方式吗?
由元素
val numbersSequence = sequenceOf("four", "three", "two", "one")
复制代码
由 Iterable
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
复制代码
由函数
要基于函数构建序列,请以该函数作为参数调用 generateSequence()
。 (可选)可以将第一个元素指定为显式值或函数调用的结果。 当提供的函数返回 null 时,序列生成停止.
以下示例中的序列是无限的:
val oddNumbers = generateSequence(1) { it + 2 } // `it` 是上一个元素
println(oddNumbers.take(5).toList())
//println(oddNumbers.count()) // 错误:此序列是无限的。
输出:
[1,3,5,7,9]
复制代码
要使用 generateSequence()
创建有限序列,请提供一个函数,该函数在需要的最后一个元素之后返回 null
。
val oddNumbers = generateSequence(1) {
if (it > 5) {
null
} else {
it + 2
}
} // it 是上一个元素
println(oddNumbers.take(5).toList())
输出:
[1,3,5,7]
复制代码
由代码块
以上的构造感觉都不够灵活,怎么自由添加元素呢?
kotlin
提供了一个函数可以逐个或按任意大小的组块生成序列元素——sequence()
函数。 此函数采用一个 lambda
表达式,其中包含 yield()
与 yieldAll()
函数的调用。
val oddNumbers = sequence {
println("Start sequence build...")
yield(1)
println("A")
yieldAll(listOf(3, 5))
println("B")
yieldAll(generateSequence(7) { it + 2 })
println("C")
}
println(oddNumbers.take(5).toList())
输出:
Start sequence build...
A
B
[1, 3, 5, 7, 9]
复制代码
yiled
和yiledAll
它们将元素返回给序列使用者,并挂起sequence()
的执行,直到使用者请求下一个元素。 yield()
使用单个元素作为参数;yieldAll()
中可以采用 Iterable
对象、Iterable
或其他 Sequence
。
可以看到发送数据的方法是suspend
方法:
public abstract suspend fun yield(value: T)
public abstract suspend fun yieldAll(iterator: Iterator<T>)
复制代码
Kotlin
编译器会把suspend
函数转化为有限状态机(算是一种优化版的回调)。这样做的好处就是只有当序列的迭代器的调用next()
后,才会去切回挂起方法里执行值的发送。
Sequence
某种意义上更像是生产 – 消费者模型中的生产者,而迭代序列的一方则像是消费者。
那么我能在里面调用其他suspend
方法吗?很遗憾并不能:
@RestrictsSuspension
@SinceKotlin("1.3")
public abstract class SequenceScope<in T> internal constructor() {}
复制代码
RestrictsSuspension
带有此注释的类和接口将受到限制,不能随意调用挂起函数。
SequenceBuilder
其并非为异步流设计,Sequence
用于同步流没什么问题,那么针对异步流我们该如何处理呢?