kotlin -- Flow 操作符②

前言

本篇文章主要介绍一下Flow常见操作符,同样的第一步,我们先引入Flow相关依赖:

implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")


 

操作符

map

首先介绍一下map操作符,跟RxJava里面的map比较类似,我们看下代码:

val flow = flowOf(1, 2, 3, 4, 5)
//map
flow.map {
    it+it
}.collect(){
    println(it)
}
  • 1
  • 2
  • 3
  • 4
  • 5

上面我们通过flowof,创建了一个数据流,然后map操作符变换,自身相加,然后打印数据,实际运行效果:

2  4  6  8  10

filter

filter 操作符,见名知意,就是过滤操作符

,看下代码:

val flow = flowOf(1, 2, 3, 4, 5)
flow.filter {
    it % 2 == 0
}.map {
    it*it
}.collect(){
    println(it)
}

 同样的,从建立的数据流,过滤了偶数,并通过map变换自身相乘,然后打印出来,看下运行效果:

4    16

onEach

onEach,简单来说就是遍历操作符,我们看下代码:

val flow = flowOf(1, 2, 3, 4, 5)
flow.onEach {
    println(it)
}.collect(){

}

结果:

1 2 3 4 5

结合用

val flow = flowOf(1, 2, 3, 4, 5)
flow.filter {
    it % 2 == 0
}.onEach {
    println(it)
}.map {
    it * it
}.collect {
  println(it)
}

结果:

 2  

 4

4

16

debounce  

debounce翻译过来的意思,就是防抖

场景:

我们在搜索输入文字时,搜索框下面会显示对应的搜索结果,怎么去请求数据合适呢?

一直去请求吗,显然不合适,我们应该是在一定时间范围,用户不在输入时,去搜索比较合适吧,这样的场景下,debounce操作符比较合适。

例子:

flow {
    emit(1)
    emit(2)
    delay(400)
    emit(3)
    delay(100)
    emit(4)
    delay(600)
    emit(5)
}
    .debounce(500)
    .collect {
        println(it)
    }

分析:

我们看到上述代码,发射1 2 中间delay 400ms,再发射3 delay 100ms,再发射4 delay 600ms,再发射5,中间debounce 500ms,1、2连续发送和3间隔400ms,3和4间隔100ms,这个都没有超过500ms,所以他们不会发送成功,4和5间隔600ms超过500ms,4发送成功,5是最后一条数据,也会发送成功,所以正常会打印4、5,我们看下实际效果:

4

5

sample

sample操作符,简单来说就是采样.

每个一段时间,执行某个操作,比如我们每个一段时间采集一条弹幕,

代码:

flow {
    while (true) {
        emit("采集一条数据")
    }
}
    .sample(1000)
    .flowOn(Dispatchers.IO)
    .collect {
        println(it)
    }

上述代码,写了一个死循环,每隔一秒采集一条数据,数据流的采集操作放在IO线程,并打印出来,我们看下实际运行效果:

在这里插入图片描述

我们看到每隔一段时间,打印了采集一条数据,和我们设想的一样。

reduce

前面我们介绍的操作符,都是要借助collect来收集的,

readuce可以自己终结flow整个流程的操作符函数,也称终端操作符函数。
reduce操作符函数,包含两个参数

flow.reduce { acc, value -> acc + value }     acc就是累积值,value当前值。

利用这个reduce函数我们可以做什么呢?

比如我们小时候经常遇到计算1-100相加的和 。

val result = flow {
    for (i in (1..100)) {
        emit(i)
    }
}.reduce { acc, value ->
    acc + value
}
println(result)

结果: 5050

fold

fold和reduce比较类似,只不过fold有个初始值,会后面的结果拼接起来。

val result = flow {
    for (i in ('A'..'Z')) {
        emit(i.toString())
    }
}.fold("Ddup2024: ") { acc, value -> acc + value}
println(result)

上述代码的意思就是,A到Z拼接起来然后合并到"Ddup2024: "后面 。

flatMapConcat

多个Flow一起操作。

场景:

我们在开发中,常常会遇到嵌套请求的问题,比如我们去拿用户信息的时候,需要先请求Token数据,再拿Token去请求用户信息,这里使用flatMapConcat比较合适。

flowOf(1,2,3)
    .flatMapConcat {
        flowOf("A$it","B$it")
    }.collect(){
        println(it)
    }

其实就是两个流按照flatMapConcat合并起来成一个flow并打印出来:

A1 B1      A2  B2      A3  B3 

按照是第一个流顺序合并, 1   2   3    。 项目上可以这么写:

fun getTokenReq(): Flow<String> = flow {
    // request to get token
    emit(token)
}

fun getUserInfoReq(token: String): Flow<String> = flow {
    // request with token to get user info
    emit(userInfo)
}

 getTokenReq()
            .flatMapConcat { token ->
                getUserInfoReq(token)
            }
            .flowOn(Dispatchers.IO)
            .collect { userInfo ->
                println(userInfo)
            }

flatMapMerge

flatMapConcat和flatMapMerge区别:

flatMapMerge函数的内部是启用并发来进行数据处理的,它不会保证最终结果的顺序。

flatMapConcat 函数的内部是启用串行来进行数据处理的,它保证最终结果的顺序。

例子:

flowOf(300, 200, 100)
    .flatMapMerge {
        flow {
            delay(it.toLong())
            emit("a$it")
            emit("b$it")
        }
    }
    .collect {
        println(it)
    }

结果:

a200   b200 

a300  b300

a100  b100

从打印的日志可以看出  没有按照第一个流的顺序合并。而是根据实际处理任务的时间先后顺序合并一起的。

flatMapLatest

flow1的数据发送过来,flow2会立即处理,但如果flow1的第二个数据发送过来,flow2第一个数据还没处理完,会直接丢弃第一个数据,去处理第二个数据。

例子:

flow {
    emit(1)
    delay(150)
    emit(2)
    delay(50)
    emit(3)
}.flatMapLatest {
    flow {
        delay(100)
        emit("$it")
    }
}
    .collect {
        println(it)
    }

这里,flow1 发送了1 2 3三个数据,flow2 100ms处理一条数据,我们可以看到1、2间隔了150ms,这时候1数据肯定处理完了,但是2数据还没处理完,3已经发送过来了,2就会丢弃,实际会打印1 3。

zip

zip函数也是两个流合并起来,和flatMap最大区别就是,zip是并行的,flatMap是一个流发送到另外一个流串行的。

tips:

这里要多讲讲, 那flatmap 不是有 flatmapMerge 和 flatmapMerge  区分么。他们是函数里面执行串行和并行。而zip 和flatmap 是流层面上的。

例子:

val flow1 = flowOf("a", "b", "c")
val flow2 = flowOf(1, 2, 3, 4, 5)
flow1.zip(flow2) { a, b ->
    a + b
}.collect {
    println(it)
}

我们可以看到flow1、flow2两个流数据量不一样,他们合并并不会像flatMap那样一一结合一起。

结果 : a1   b2   c3 

buffer

我们之前使用RxJava,肯定听过背压问题,就是通过Observable发射,处理,响应数据流时,发送、处理数据之间速度不平衡,导致缓存池未来得及处理的数据就会造成积压,最终造成内存溢出,响应式编程中的背压(Backpressure)问题。同样的Flow,处理背压问题,会使用到介绍来介绍的三个函数。

flow {
    emit(1);
    delay(1000);
    emit(2);
    delay(1000);
    emit(3);
}.onEach {
    println("$it is ready")
}.collect {
    delay(1000)
    println("$it is handled")
}

我们在flow1发送每隔1秒发送了一条数据,onEach打印了发送的数据,然后我们花费一秒时间处理一条数据。
我们看下运行效果:

buffer.gif

上述运行效果,也按我们前面说的一样执行,但是你有没有发现,我们2秒钟才处理了一条数据,我们稍加改造一下:

flow {
    emit(1);
    delay(1000);
    emit(2);
    delay(1000);
    emit(3);
}.onEach {
    println("$it is ready")
}.buffer()
    .collect {
        delay(1000)
        println("$it is handled")
    }

我们可以看到,上述代码,只是增加了一个buffer函数,我们再看下运行效果:

在这里插入图片描述

我们可以看到处理数据比之前相对较快了。

buffer函数的作用会让flow函数和collect函数运行在不同的协程当中,这样flow中的数据发送就不会受collect函数的影响了。
buffer函数其实提供了一块缓存区,当Flow数据流速不均匀的时候,使用这份缓存区来保证程序运行效率。
flow函数只管发送自己的数据,无需关心数据是否处理,反正数据会缓存在buffer中。
collect函数,只需要一直从buffer中获取数据,然后处理就行。
但是,可以思考这样一个问题,有必要每个数据都缓存下来,可不可以丢弃部分数据,丢弃数据如何处理呢?
接下来,我们可以了解一下conflate函数。

conflate

正和我们前面说的一样,处理数据流速不均的问题,我们可以采取丢弃部分数据来解决。

flow {
    var count = 0
    while (true) {
        emit(count)
        delay(1000)
        count++
    }
}.collectLatest {
    println("start handle $it")
    delay(2000)
    println("finish handle $it")
}

这里我们使用了collectLatest函数,我们写了个死循环,每隔一秒改变一下count的值,collectLatest函数收集数据并打印,然后最后延迟2秒,打印完成处理。
我们看下实际运行效果:

conflate1.gif

我们可以看到一直在打印收集的最新的数据,但是处理完成的消息没有打印,collectLatest函数的特性就是当有新数据到来时而前一个数据还没有处理完,则会将前一个数据剩余的处理逻辑全部取消。
假如我们需要当前正在处理的数据无论如何都应该处理完,然后准备去处理下一条数据时,直接处理最新的数据即可,中间的数据就都可以丢弃掉了。我们可以使用到conflate函数。

前面的代码,改造如下:

flow {
    var count = 0
    while (true) {
        emit(count)
        delay(1000)
        count++
    }
}
    .conflate()
    .collect {
        println("start handle $it")
        delay(2000)
        println("finish handle $it")
    }

我们再运行一下,看下效果:

conflate2.gif

我们可以看到,每条数据都从头到尾处理完毕,中间错过的数据,直接丢弃,然后处理下条数据。

总结

至此,我们基本flow常见的函数操作符介绍完毕了。

下一篇: kotlin -- Flow在实际项目中使用-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值