kotlin 协程之异步流的缓冲、折叠与组合

190 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第22天,点击查看活动详情

缓冲操作符

buffer缓冲操作符可以打破流顺序运行的状态,并发运行流中元素的发射和收集。以同一个流的发送和处理为例,添加buffer()之后处理总时长比没有添加buffer()的总时常短200ms左右。代码和运行结果如下:

 fun simple4():Flow<Int> = flow {
   for (i in 1..3){
     delay(100)
     emit(i)
   }
 }
 val simple=testFlow.simple4()
 val timeWithoutBuffer= measureTimeMillis {
   simple.collect { value ->
                   delay(300)
                   println(value)
                  }
 }
 println("Collected in $timeWithoutBuffer ms")
 ​
 val timeWithBuffer= measureTimeMillis {
   simple.buffer().collect { value ->
                            delay(300)
                            println(value) }
 }
 println("Collected in $timeWithBuffer ms")

1

2

3

Collected in 1265 ms

1

2

3

Collected in 1089 ms

折叠/合并操作符

conflate:当收集器处理太慢且不需要对每个值都进行收集处理时,添加conflate操作符可以跳过中间来不及收集处理的值,只接收最新的值进行处理。以每次延时100ms发送30个值为例,第一个值和最后一个值必被收集。每一次收集延时600ms,在第一个值还没被收集处理完时,第2~5个值已经发出。此时收集器仍在处理第一个值,后面发出的这四个值都会被折叠,不会被收集处理。当第一个值处理完时,收集器空闲,第6个值发出被收集。代码和运行结果如下:

 fun simple4():Flow<Int> = flow {
   for (i in 1..30){
     delay(100)
     emit(i)
   }
 }
 val simple=testFlow.simple4()
 simple.conflate() //折叠
 .onEach { delay(600) }  //每次延时600ms
 .collect { value ->
           println(value)
          } 

1

6

12

18

24

29

30

collectLatest:当收集器内进行耗时操作时,只对最后一个值进行收集处理(完成耗时操作),前面所有的值只进行收集而不处理(不进行耗时操作)。如下:发送四个值,收集器中有一个300ms的延时动作。前面的三个值都没有响应延时动作,只有最后一个值完整地响应收集器动作。

 fun simple4(): Flow<Int> = flow {
   for (i in 1..4) {
     delay(100)
     emit(i)
   }
 }
 val simple = testFlow.simple4()
 val time = measureTimeMillis {
   simple.collectLatest { value ->
                   println("Collecting $value")
                   delay(300)
                   println("Done $value")
                  }
 }
 println("Collected in $time ms")

Collecting 1

Collecting 2

Collecting 3

Collecting 4

Done 4 Collected in 815 ms

组合多个流

zipcombine用于组合两个流。区别在于: zip会通过两个流的发送顺序匹配收集的值,只有当两个流的值都到达时(对时间没有要求)收集器才会收集处理。当两个流数量不同时,zip只响应区间相同的部分。 而combine不进行匹配,只要有一个值到达,收集器就会从上次保存的值中取出另一个值进行响应处理。以下代码为例,流1有三个值,流2有四个值,使用zip操作符组合后只收集到三个值,使用combine操作符组合后收集到6个值。

 val number=(1..3).asFlow()
 val fruit= arrayListOf("apple","bananar","watermelon","pear").asFlow()
 number.zip(fruit){a,b->"$a -> $b"}
 .collect { println("zip:$it") }
 number.combine(fruit){a,b->"$a->$b"}
 .collect { println("combine:$it") }

zip:1 -> apple

zip:2 -> bananar

zip:3 -> watermelon

combine:1->apple

combine:2->apple>

combine:2->bananar

combine:3->bananar

combine:3->watermelon

combine:3->pear

当要组合的两个流不同步发射时,zip会等待两个值到达,匹配收集,combine是只要有值到达就进行收集处理。扩展上面的例子,流1的发送延时300ms,流2的发送延时400ms。zip操作符每次收集时间在400ms左右(每组值的发送总延时,即流2的发送延时)。而combine操作符的每次收集时间是不一定的,除了第一组收集需要等待两个流的值全部到达,剩下每组值的收集时间是每组流中最新到达的值的时间与上一组值的收集时间的差值。

 val number=(1..3).asFlow().onEach { delay(300) } //3个值,间隔300ms
 val fruit= arrayListOf("apple","bananar","watermelon","pear").asFlow().onEach { delay(400) } //4个值,间隔400ms
 var startTime=System.currentTimeMillis()
 //zip
 number.zip(fruit){a,b->"$a->$b"}
 .collect { println("$it at ${System.currentTimeMillis()-startTime}ms from zip start") }
 startTime=System.currentTimeMillis()
 //combine
 number.combine(fruit){a,b->"$a->$b"}
 .collect { println("$it at ${System.currentTimeMillis()-startTime}ms from combine start") }

1->apple at 440ms from zip start

2->bananar at 842ms from zip start

3->watermelon at 1247ms from zip start

1->apple at 445ms from combine start

2->apple at 689ms from combine start

2->bananar at 851ms from combine start

3->bananar at 993ms from combine start

3->watermelon at 1255ms from combine start

3->pear at 1657ms from combine start