很多人觉得Flow很难学,学不会。那是因为学习方法不对。
因为你没有学Sequence,直接学Flow,确实容易迷茫。
什么是Sequence?
Sequence和Collection有什么不同?
什么时候该用Sequence,什么时候该用Collection。
一、学Flow,为什么大叔推荐你先学Sequence呢?
因为Flow比Sequence更强大,支持的操作更丰富。
这句话的另一面就是:Sequence比Flow更简单,更容易上手。
我们可以简单理解为Flow是支持异步操作的Sequence。
加了异步操作之后学习难度直线上升。
如果你学习Flow感觉到困难,或者迷茫,先学好Sequence吧。
二、什么是Sequence ---- Sequence也是一种容器
Sequence和Collection都是kotlin的容器。
和集合的List、Queue、Set一样,Sequence也能动态管理多个数据。
在kotlin中,Sequence和Collection有很多相同的操作函数,如:map,filter,等。
所以很自然Sequence也可以通过迭代器操作。

三、数据处理顺序:Collection vs Sequence
注意:理解数据处理顺序非常重要,这是Sequence的精髓所在。
注意:理解数据处理顺序非常重要,这是Sequence的精髓所在。
1. Collection批量处理数据
val wordList = listOf("The","quick","brown", "fox", "jumps", "over", "the", "lazy", "dog")
val lengthsList = wordList
.filter { it.length > 3 }
.map { it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars:")
println(lengthsList)
如上代码,wordList中的单词处理顺序,如图:

如图,集合的每一个操作都会返回一个新的集合,然后对新的集合做下一个操作。
- 对集合wordList进行Filter操作,将先生成一个新的集合
Collection1
=={"quick","brown", "jumps", "over", "lazy"}- 然后对
Collection1
执行map操作,又将生成一个新的集合Collection2
=={5,5,5,4,4}- 然后
Collection2
执行take操作,最后返回新的集合Collection3
=={5,5,5,4}为了得到集合
Collection3
,产生两个中间集合Collection1
、Collection2
.可以想象操作越多,越浪费内存。
2. Sequence流式处理数据
val wordsSequence = listOf("The","quick","brown", "fox", "jumps", "over", "the", "lazy", "dog")
.asSequence()// 将列表转换为序列
val lengthsSequence = wordsSequence
.filter { it.length > 3 }
.map { it.length }
.take(4)
println("Lengths of first 4 words longer than 3 chars")
println(lengthsSequence.toList())// 末端操作:以列表形式获取结果。
如上代码,wordList中的单词处理顺序,如图:

如图,Sequence中的元素,按顺序一个一个处理。
元素1
先执行完全部操作,然后元素2
再执行全部操作……(当然,某个元素,可能在某操作中被过滤掉,就不会被后面的操作处理),最后返回结果。
- wordList中的第一个元素
The
,经过filter操作,被过滤掉了,所以不会进行后面的操作。- 第二个元素
quick
,先对quick
执行filter
操作,filter通过,然后对quick
执行map操作,生成了元素5
,再对5
执行take操作,添加到列表中。- 依次类推,第三个元素、第四个元素……当take(4),收集满4个元素之后,后面的元素就不会再遍历了
Sequence这种操作方式就像IO流,当我们读磁盘上的一个文件时,读取数据就流水一样,一波一波的流向InputStream,而不是等全部数据加载完成后再返回给InputStream。
3. 流式操作的优势
更加灵活,高效,内存消耗低。
可以处理大量数据的情况。
3.1、更高效的处理大量数据
假如我们数据库有100万个单词,需要执行上面demo的filter、map、take的操作。
Collection:
val wordList = queryAMillionWrods()//查询100万条数据
val lengthsList = wordList
.filter { ... }
.map { ... }
.take(4)
...
使用Collection进行,必须要先将100万条数据全部读取并存入集合wordList,然后每个操作又会产生临时集合。
想想都觉得浪费啊,兄弟们~
而且性能也是不敢想象,我们只需要取四个符合条件的单词即可。
但是我们却对100万条数据全部进行了遍历。
Sequence
val wordsSequence = sequence {
for(i in 1..10000){
val wordList = queryAThousandWrods(i)//分10000次查询,每次查100个单词
yieldAll(wordList)//将100条数据发射出去
}
}
val lengthsSequence = wordsSequence
.filter { ... }
.map { ... }
.take(4)
println("Lengths of first 4 words longer than 3 chars")
println(lengthsSequence.toList())
妙啊,筒子们,是不是很妙。
- 整个过程只产生了一个列表,lengthsSequence.toList()
当取满4个符合条件的单词,就不会再进行遍历了。for循环会被自动中断掉。
所以很可能,只循环一次就结束了。
假如第一次查询的到的100个单词里,就已经有4个满足条件的单词了,那么不会触发第二次循环。
就问你精彩不精彩啊,筒子们 ~
3.2、轻松实现数据的订阅
这也就是大名鼎鼎的StateFlow的技能。
这个我们后面再单独讲。
四、触发遍历数据的时机:Collection vs Sequence
我们来看下,如下两端代码:
集合Collection:
listOf("The","quick","brown").map {
println("map $it")
it
}
序列Sequence:
listOf("The","quick","brown").asSequence().map {
println("map $it")
it
}
上面两段代码,分别会打印什么?
上面两段代码,分别会打印什么?
上面两段代码,分别会打印什么?
答案是:
第一段代码,map中会打印每个元素。
第二段代码Sequence不会打印任何日志。
为什么呢?
Collection的每个操作基本都立即执行,并返回一个新的集合。
Sequence的操作分为中间操作(intermediate operations)和末端操作(terminal operations)。
Sequence的操作函数如果返回值的类型是Sequence,那么这个操作就是一个中间操作。这些操作并不会触发数据的发射和遍历。
例如如下都是中间操作:
map(transform: (T) -> R): Sequence<R>
filter(predicate: (T) -> Boolean): Sequence<T>
take(n: Int): Sequence<T>
//等等
否则这个操作就是末端操作。只有对Sequence执行末端操作才会触发数据的发射和遍历。
我们也称这种数据流为冷流。
例如如下都是末端操作:
forEach(action: (T) -> Unit): Unit
first(): T
count(): Int
fold(initial: R, operation: (acc: R, T) -> R): R
//等等
五、创建Sequence
1. sequenceOf
val numbersSequence = sequenceOf("four", "three", "two", "one")
2. Iterable.asSequence 将任一集合转化为 Sequence
val numbers = listOf("one", "two", "three", "four")
val numbersSequence = numbers.asSequence()
3. 通过 generateSequence 函数构造Sequence
通过generateSequence创建一个序列。
无穷大的序列:
fun createSequence1(){
val firstElement = 3
//class kotlin.sequences.ConstrainedOnceSequence
val sequence = generateSequence(firstElement) {
it + 2
}
val result = sequence.take(5).toList()
println("result=$result")//result=[3, 5, 7, 9, 11]
//println("sequence2 count=${sequence.count()}")//会导致死循环,因为这个序列是无穷大
}
有限大小的序列:
当generateSequence遇到第一个null元素就认为所有element结束了。null不会当着element。
fun createSequence2(){
val firstElement = 3
val sequence = generateSequence(firstElement) {
if (it < 10) {
it + 2
}else if(it < 20){
null
}else {
it + 20
}
}
val result = sequence.toList()
println("result=$result")//result=[3, 5, 7, 9, 11]
println("count=${sequence.count()}")//count=5
}
4. 通过sequence+yield函数构造Sequence
fun createSequence3() {
//class kotlin.sequences.SequencesKt__SequenceBuilderKt$sequence$$inlined$Sequence$1
val seq = sequence {//这是一个协程体
println("sequence.begin")
println("yield 1")
yield(1)
println("yield 2")
yield(2)
println("yieldAll 3,4,5")
yieldAll(listOf(3, 4, 5))
println("yieldAll 6, 7, 8")
yieldAll(sequenceOf(6, 7, 8))
println("yieldAll 9, 10, 11")
yieldAll(generateSequence(9) {
if (it <= 11) {
it + 1
} else {
null
}
})
println("sequence.end")
}
val result = seq.toList()
println("result=$result")//result=[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
val count = seq.count()
println("count=$count")//count=12
}
sequence函数
public fun <T> sequence(@BuilderInference block: suspend SequenceScope<T>.() -> Unit): Sequence<T>
@RestrictsSuspension
public abstract class SequenceScope<in T> internal constructor() {
public abstract suspend fun yield(value: T)
public abstract suspend fun yieldAll(iterator: Iterator<T>)
public suspend fun yieldAll(elements: Iterable<T>)
public suspend fun yieldAll(sequence: Sequence<T>)
}
4.1 sequence函数用来创建Sequence对象。
sequence函数的参数block是一个协程体。并且是SequenceScope的匿名拓展函数。
4.2 yield、yieldAll函数用来向流中发射数据。
SequenceScope.yield(value)函数是SequenceScope的成员函数。
yield 是挂起函数,所以必须在协程中执行。并且协程的Scope必须是SequenceScope类型。
如下代码将报错:

错误提示:
Restricted suspending functions can only invoke member or extension suspending functions on their restricted coroutine scope
----
yeild()函数的调用只能限制在规定的coroutine scope内。也就是SequenceScope。
kotlin如何做到这种约束呢?
因为SequenceScope的注解RestrictsSuspension。
4.3 重要总结:
- sequence的参数block是一个协程体,并且就是SequenceScope的拓展函数。
- SequenceScope.yield(value)是挂起函数,只能在SequenceScope的协程体中执行。
六、Sequece和Flow的区别
Sequece没有切换线程的操作,更没有切换协程的操作。
也就是说,Sequence链上的操作都执行在同一个线程中。
Flow:官方称之为Asynchronous Flow,异步流。异步是Flow和Sequence的最重要区别。
异步流比常规的多协程
、多线程
概念似乎更复杂一丢丢,因为它还有并行流
的概念。
以后我们讲Flow的时候,详细讲讲并行流
。
如下Flow操作,Sequence没有这些操作:
1、Flow.flowOn(context: CoroutineContext)
2、Flow.launchIn(scope: CoroutineScope): Job
3、Flow.buffer(capacity: Int = BUFFERED)
flatMapMerge()
collect()
conflate()
combine()
catch()
//等等
作者:IT互联网大叔
链接:https://juejin.cn/post/7066832858664402980
如有侵权,请联系删除!
网友评论