Kotlin-Flow

173 阅读4分钟

在学习Flow之前我们先了解一下相关定义

1:什么是数据流和异步数据流:

数据流: 是一种,数据连续地传输和处理数据的方式.

异步数据流: 一种异步的,连续的传输和处理数据的方式.

数据流组成:

  • 生产者:生成数据,添加到数据流中
  • 加工者:处于数据流中间,可以对数据流中的数据进行各种变换、加工等操作
  • 消费者:处于数据流的末尾,对数据流中的数据进行最终的消费

个人理解: 就像是水从高处流动到低处的流水线,在这个过程中可以对他加工,最后得到一个经过加工过的饮料产品.

2:冷流和热流

2.1 冷流

冷流:是指数据或事件在被订阅(监听)后才开始生成,类似于 “按需触发”.一旦有订阅者加入,流才会从头开始生成数据。

核心特点

  1. 懒加载(Lazy Evaluation)

    • 数据生成依赖订阅者的存在,订阅前不产生数据。
    • 示例:函数调用返回的迭代器(如 Python 的生成器),仅在调用next()时生成下一个值。
  2. 多订阅者隔离

    • 每个订阅者独立触发流的生成,拥有独立的数据序列。
    • 示例:多个用户同时请求接口,服务端为每个请求单独生成数据流(如 HTTP 接口的响应)。
  3. 无历史数据

    • 订阅者只能获取订阅后产生的数据,无法追溯历史数据。
    • 示例:实时日志接口,新订阅的客户端只能收到订阅后的新日志。

2.2 热流

热流:是指数据或事件的生成独立于订阅者,无论是否有订阅者,流都会持续产生数据.订阅者加入时,可能获取当前或历史数据(取决于实现)

核心特点

  1. 主动生成数据

    • 流的生命周期独立于订阅者,可能在程序启动时就开始运行(如实时传感器数据采集)。
  2. 多订阅者共享状态

    • 所有订阅者监听同一个数据源,可能获取相同的实时数据或历史缓冲数据。
    • 示例:消息队列(如 Kafka)的主题(Topic),新消费者可从指定偏移量(offset)获取历史消息。
  3. 支持历史数据回溯

    • 部分热流实现(如带有缓冲区的流)允许订阅者获取订阅前的部分数据。
    • 示例:Redis 的发布 - 订阅(Pub/Sub)机制,若消息被持久化,新订阅者可读取历史消息。

冷流 vs 热流:核心对比

维度冷流(Cold Stream)热流(Hot Stream)
数据生成触发订阅者调用subscribe()后开始生成独立于订阅者,主动持续生成
订阅者隔离性每个订阅者获取独立的数据流所有订阅者共享同一数据流
历史数据支持不支持(仅订阅后的数据)支持(部分实现可回溯历史数据)
典型场景请求 - 响应、文件读取、懒加载数据实时事件、消息队列、持续数据源
内存占用按需生成,内存压力较小可能需要缓存历史数据,内存占用较高

总结: 冷流适合按需触发的场景,热流适合实时共享的场景

3.Flow是什么??

代码例子:

object FlowTest {
    @JvmStatic
    fun main(args: Array<String>) {
        flowTest()
    }

    fun flowTest(){
    //生产
        val flow = flow {
            println("flow start")
            emit(1)
            println("flow end")
            emit(2)
            println("flow end")
        }
        println("go")
        //异步执行
        GlobalScope.launch{
        //消费 map 加工 collect 消费
            flow.map {it+1}
            .collect {
                println("collect $it")
            }
        }
        println("stop")
        Thread.sleep(2000)
    }
}

结果:
stop
flow start
collect 2
flow end
collect 3
flow end


Flow 是一种异步数据流在Kotlin中的实现.

Flow 是冷流:只有当流被 ** 收集(collect)** 时才会开始发射数据,且每个收集器独立触发流的执行

异步性: 接上边解释 Flow是一个异步数据流异步是怎么来的呢??

协程赋予了Flow的异步属性 因为collect()方法是一个挂起方法 必须在协程作用域中调用.

3.1 Flow实现的组成

生产者 加工者 消费者

3.1.1 生产者(构建器)

  • flow { ... }:创建一个可挂起的冷数据流,通过 emit() 发射值

  • flowOf(value1, value2):创建固定值的流

  • list.asFlow():将集合转换为 Flow

  • channelFlow { ... }:基于 Channel 的热数据流(支持主动发送

3.1.2 加工者(操作符)

  • 转换操作maptransformflatMapConcat
  • 过滤操作filtertakedrop
  • 合并操作zipcombineflowOn(切换协程上下文)。
  • 背压处理bufferconflatecollectLatest

3.1.3 消费者(搜集)

  • 触发收集collectcollectIndexed

3.2 例子


fun main() = runBlocking {
    val flow = flow {
        for (i in 1..5) {
            delay(100)
            emit(i)
        }
    }
    .flowOn(Dispatchers.Default)  // 切换执行上下文
    .map { it * 2 }               // 转换数据
    .filter { it > 5 }            // 过滤数据
    .buffer(10)                   // 背压缓存
    .catch { e -> emit(-1) }      // 异常处理
    
    flow.collect { value ->
        println("Collected: $value")
    }
}

// 输出:
// Collected: 6
// Collected: 8
// Collected: 10

4. Flow类型

4.1冷流(Flow)

数据生产者与消费者绑定:冷流是懒惰的,数据只有在有消费者(collect)时才开始生产。这意味着每个新的消费者会触发一个新的数据流。

适合单播场景:冷流更适合于单播场景,即每个消费者独立消费数据,不受其他消费者的影响

4.2热流(StateFlow(状态)、SharedFlow(事件),MutableSharedFlow、MutableStateFlow)

  • 数据生产者独立于消费者:热流会在创建后立即开始生产数据,而不管是否有消费者。这意味着数据的生产和消费是独立的。
  • 适合多播场景:热流更适合于多播场景,即同一个数据流可以被多个消费者同时消费。

总结

StateFlow:适合于状态管理场景,例如在ViewModel中表示UI状态 SharedFlow:更加灵活和通用,适用于事件处理事件总线,消息队列等场景。尤其适合需要重播特定数量的历史事件或者处理事件丢弃政策的场景