(原创)Flow数据流的使用

news2024/11/8 6:38:41

前言

这篇文章主要介绍Flow的一些基础使用方法
同时介绍如何用Flow请求网络数据
下面开始!

什么是Flow

Flow翻译过来,是“流”的意思
举例说明,在大自然中,常见的如水流
是从高往低流动的
那么在计算机世界里,所谓的“流”
其实指的是数据流
也就是从获取原始数据,到进行处理,最后使用的过程
比如拿到一个json,转换为bean
然后进行筛选和过滤
拿到最后要用的最终数据
这个过程就称之为数据的流动
如下图:
在这里插入图片描述
而为了处理这个过程
我们就可以使用到Flow这个工具

Flow流的使用

简单使用

要使用Flow,首先需要导入协程相关工具类:

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.7-mpp-dev-11'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.7-mpp-dev-11'

最简单的Flow使用:

suspend fun flow1() {
    flow<Int> {
        (0..4).forEach {
            emit(it)//生产者发送数据
        }
    }.collect {
        Log.d("flow1", "it:$it")//消费者处理数据
    }
}

我们分析下这段代码:
1:(0…4)是一个0-4的列表,这个是原始数据
2:emit其实就是把原始数据发送出去
3:collect用来收集发送的原始数据,并且内部自己打印了收到的数据
4:flow { } 函数包裹的代码块就是负责发送数据的,这个函数返回一个Flow对象
注意:Flow流是“冷流”
意思是 collect 被调用后 flow 内的方法体才会被调用
如果没有collect方法,不管如何emit,数据都是不会发送出去的

流操作符

Flow对于数据的操作,为我们提供了一系列的API
我们称之为“操作符”
一般分为“流构建器”、“中间操作符”和“末端操作符”
流构建器一般用来构建Flow流对象
中间操作符仅仅只是预先定义一些对流的操作方式,
比如过滤,转换等
并不会主动触发动作执行
而末端操作符则是对流的最终处理
比如collect就是末端操作符
下面就介绍一些操作符

流构建器

flowof

可以将 flowOf 内的可变长参数一一发射

flowOf(1, 2, 5, 4).collect {
        println(it)
}

asFlow

flowOf 可以将集合转换成 flow 发射

suspend fun asFlowM(){
    listOf(1,2,9,0,8).asFlow().collect{
        println(it)
    }
}

中间操作符

map

我们可以再 map 中执行一些过渡操作,
比如本例中将生产者发送的数据*9,然后再发射给消费者
值得一提的是,我们是可以再 map 中进行异步操作的
注意,这个map和集合没什么关系,别被误导了

suspend fun mapM(){
    (1..9).asFlow().map {
        it*9
    }.collect{
        println(it)
    }
}

transform

transform 主要强调的是类型的转换

(1..3).asFlow() // 一个请求流
        //transform中的泛型<Int,String> 表示将Int类型转换为String后,继续发射
        .transform<Int, String> { request ->
            emit("transform Int to String $request")
        }
        .collect { response -> println(response) }

take

限长操作符 take 可以限定我们要消费的数据的数量,见代码

(1..9).asFlow().take(3).collect {
        println(it)
}

conflate

当生产者发射数据速度大于消费者的时候,消费者只能拿到生产者最新发射的数据

suspend fun conflate(){
    flow<Int> {
        (1..9).forEach {
            delay(100)
            emit(it)
        }
    }.conflate().collect {
        delay(300)
        println(it)
    }
}

比如上面这段代码,因为有conflate的存在,输出如下:

1
3
6
9

如果没有conflate存在输出如下:

1
2
3
4
5
6
7
8
9

两者对比,明显能发现使用conflate的例子替我们忽略了很多无法即时处理的数据

collectLast

这个操作符的意思:如果生产者数据以及发射过来了,消费者还没有把上一个数据处理完,那么直接停止处理上一条数据,直接处理最新的数据


suspend fun collectLastM(){
    flow<Int> {
        (1..9).forEach {
            delay(100)
            emit(it)
        }
    }.collectLatest {
        delay(800)
        println(it)
    }
}

比如本例的输出为9

zip

zip操作符可以把两个流合并为一个流,然后再zip方法中将两个流发射的数据进行处理组合后继续发射给消费者,
如果两个流长度不一致,按比较短的流来处理:
1.两个流长度一致,都是3

suspend fun zipM(){
    val flow1 = (1..3).asFlow()
    val flow2 = flowOf("李白","杜甫","安安安安卓")
    flow1.zip(flow2){a,b->
        "$a : $b"
    }.collect {
        println(it)
    }
}

输出:

1 : 李白
2 : 杜甫
3 : 安安安安卓

上面的代码我们进行一下改变,将flow1的长度改为5

val flow1 = (1..5).asFlow()

查看输出结果:

1 : 李白
2 : 杜甫
3 : 安安安安卓

所以验证一下我们开头的结论,两个长度不同的流zip合并,消费者输出的数据长度是较短的流的长度

combine

上一节zip的缺点我们清楚了,就是两个流长度不等的时候,较长的流后面部分无法输出

那么combine就是用来解决zip这个缺点的(也很难说是缺点,只是应用场景不同罢了,你姑且可以认为是缺点)


suspend fun combineM(){
    val flowA = (1..5).asFlow()
    val flowB = flowOf("李白","杜甫","安安安安卓")
    flowA.combine(flowB){a,b->
        "$a : $b"
    }.collect {
        println(it)
    }
}

输出日志:

1 : 李白
2 : 李白
2 : 杜甫
3 : 杜甫
3 : 安安安安卓
4 : 安安安安卓
5 : 安安安安卓

我们的两个流,数字流长度为5,字符串流为3。

实现的效果简单逻辑分析:

flow发射1,flow2发射 ”李白“ ,打印:1 : 李白
flow发射2,flow2未发射数据  ,打印:2 : 李白
flow未发射,flow2发射 ”杜甫“ ,2 : 杜甫
flow发射3,flow2未发射 ,打印:3 : 杜甫
flow未发射,flow2发射 ”安安安安卓“ ,打印:3 : 安安安安卓
flow发射4,flow2发射完成  ,打印:4 : 安安安安卓
flow发射5,flow2发射完成  ,打印:5 : 安安安安卓

onCompletion

使用onCompletion可以再流完成的时候再发送一个值

 flowOf(1, 23, 5, 3, 4).onCompletion {
        println("流操作完成")
        emit(12344)//这里不返回值也没关系
    }.collect {
        println(it)
    }

输出:

1
23
5
3
4
流操作完成
12344

末端操作符

toList

会把数据消费到一个 List 列表中

suspend fun toList():List<Int> {
   return (1..9).asFlow().filter { it % 2 == 0 }.toList()
}

toSet

同 toList

frist

获取第一个元素

suspend fun firstM(): Int {
    return (2..9).asFlow().filter { it % 2 == 1 }.first()
}

reduce

reduce 的兰布达表达式会提供运算公式负责计算。

在 reduce 的兰布达表达式中,可以对当前要消费的值和之前计算的值进行计算,得到新值返回。所有值消费完成后返回最终值

suspend fun reduceM():Int {
    return (1..9).asFlow().reduce { accumulator, value ->
        println("$accumulator : $value")
        accumulator + value
    }
}

buffer

buffer可以缓存生产者数据,不会被消费者阻塞

suspend fun bufferM() {
    val startMillis = System.currentTimeMillis()
    flow<Int> {
        (1..3).forEach {
            delay(300)
            emit(it)
        }
    }.buffer(4)
        .collect {
            delay(400)
            println(it)
            println("时间已经过了${System.currentTimeMillis() - startMillis}")
        }
}

代码执行打印日志:

1
时间已经过了745
2
时间已经过了1148
3
时间已经过了1552

如果我们没有用buffer,那么总时长应该2100ms
使用了buffer总时长是:1552=300+400*3
所以使用buffer的时候生产者可以并发发射数据,不会被消费者阻塞

流异常

使用try/catch包裹流
我们是可以使用try/catch来收集流异常的,但是不建议用这种方法
使用flow的catch操作符处理流
使用flow 的catch操作符处理异常更优雅
不过catch也有缺点,它只能捕获生产者的异常不能捕获消费者的异常


suspend fun trycatch() {
    flow<Int> {
        (1..3).forEach {
            if (it == 2) {//故意抛出一个异常
                throw NullPointerException("强行空指针,嘿嘿嘿嘿")
            }
            emit(it)
        }
    }.catch {e->
        e.printStackTrace()
        emit(-1)//异常的情况下发射一个-1
    }.collect{
        println(it)
    }
}

消费者的异常如何处理
尝试在消费者中抛出异常,查看是否可以被捕获

 flow<Int> {
        for (i in 1..3) {

            emit(i)
        }
    }.catch {
        emit(-1)
    }.collect {
        if(it==2){//在消费者中抛出数据
            throw IllegalArgumentException("数据不合法")
        }
        println(it)
    }

输出:

1
Exception in thread "main" java.lang.IllegalArgumentException: 数据不合法
	at HahaKt$consumerCatch$$inlined$collect$1.emit(Collect.kt:138)

将异常代码放在onEach中catch异常

suspend fun consumerCatch() {
    flow<Int> {
        for (i in 1..3) {

            emit(i)
        }
    }.onEach {
        if (it == 2) {//与上面的不同,在消费之前先用onEach处理一下
            throw IllegalArgumentException("数据不合法")
        }
    }.catch {
        emit(-1)
    }.collect {
        println(it)
    }
}

输出:

1
-1

相关资料

Kotlin Flow详解
Kotlin Flow啊,你将流向何方?
官方 flow 地址
使用 Kotlin Flow 构建数据流 “管道”

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/142079.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

loss盘点: asl loss (Asymmetric Loss) 代码解析详细版

1. BCE公式部分 可以简单浏览下这篇博客的文章&#xff1a; https://blog.csdn.net/qq_14845119/article/details/114121003 这是多分类 经典 BCELossBCELossBCELoss 公式 L−yL−(1−y)L−L -y L_{} - (1-y) L_{-} L−yL​−(1−y)L−​ 其中&#xff0c;L/−L_{/-}L/−​…

Docker保姆级学习教程

文章目录1、什么是Docker1.1、容器技术1.2、容器与虚拟机比较1.3、Docker特点1、更高效的利用系统资源2、更快速的启动时间3、一致的运行环境4、持续支付和部署5、更轻松的迁移6、更轻松的维护和扩展2、Docker组件学习2.1、Docker客户端和服务器2.2、Docker镜像2.3、Registry&a…

奇怪的知识——Windows下怎么修改进程的名称?

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;无尽的折腾后&#xff0c;终于又回到…

element-plus的form表单form-item的prop怎么写才能正确校验,实现逻辑是怎么样的?

不管是element-plus还是上一个版本的element-ui&#xff0c;都是一个使用很广泛的基于csshtmljs的ui组件库&#xff0c;它的form表单自带强大的校验功能&#xff0c;form-item的prop怎么写才正确&#xff0c;实现逻辑是怎么样的&#xff1f;element-plus的form表单的model、for…

聚观早报 | 苹果市值跌破2万亿美元大关;卢伟冰晋升小米集团总裁

今日要闻&#xff1a;苹果市值跌破2万亿美元大关&#xff1b;卢伟冰晋升小米集团总裁&#xff1b;京东方拿下iPhone 15订单&#xff1b;英伟达与富士康达成合作&#xff1b;哪吒汽车旗下车型价格调整苹果市值跌破2万亿美元大关 1 月 4 日消息&#xff0c;据国外媒体报道&#x…

C51单片机连接wifi模块,发送AT指令

一、AT指令AT 指令集是从终端设备&#xff08; Terminal Equipment &#xff0c; TE) 或 数据终端设备 &#xff08; Data TerminalEquipment &#xff0c; DTE) 向终端适配器 (Terminal Adapter &#xff0c; TA) 或 数据电路终端设备 (Data CircuitTerminal Equipment &#…

CDGA|企业数字化转型进展得越快就越好吗?

数据治理并不是一件简单的事情。即使是行业知名公司&#xff0c;在高度重视和确保投入的情况下&#xff0c;完成全公司“数据底座”/“数据中台”的所耗时间也往往以年计。并且&#xff0c;还需要注意到&#xff0c;数据规范只是数字化转型的一个维度而已&#xff1a; 在国标《…

SQL INSERT INTO 语句

INSERT INTO 语句用于向表中插入新记录。 SQL INSERT INTO 语句 INSERT INTO 语句用于向表中插入新记录。 SQL INSERT INTO 语法 INSERT INTO 语句可以有两种编写形式。 第一种形式无需指定要插入数据的列名&#xff0c;只需提供被插入的值即可&#xff1a; INSERT INTO t…

Python爬虫常用哪些库?

经常游弋在互联网爬虫行业的程序员来说&#xff0c;如何快速的实现程序自动化&#xff0c;高效化都是自身技术的一种沉淀的结果&#xff0c;那么使用Python爬虫都会需要那些数据库支持&#xff1f;下文就是有关于我经常使用的库的一些见解。 请求库&#xff1a; 1、urllib&a…

matlab复杂函数多元函数拟合

简介 本文介绍了基于matlab实现的复杂函数以及多元函数的拟合。在工程和研究中偶尔会遇到要用一个非常复杂的数学公式来拟合实验测量数据&#xff0c;对这些复杂的数学公式拟合时&#xff0c;采用常见的拟合方法往往会失败&#xff0c;或者得不到足够精确的结果。本文以笔者多…

AVL树:高度平衡的二叉搜索树

AVL树 AVL树和BST树的联系   答&#xff1a;BST树&#xff08;二叉排序树&#xff09;当节点的数据key有序时是一棵单支树&#xff0c;查找时效率直接降低到O(N)而不是树高&#xff0c;为了使树尽量两边均匀&#xff0c;设计出了AVL树&#xff0c;AVL树的左右高度差不超过1。…

sql语句练习题1

1、选择部门30中的所有员工&#xff1b; 要注意到查的是所有员工 代码如下&#xff1a; mysql> select * from emp where deptno 30;2、列出所有办事员(CLERK)的姓名&#xff0c;编号和部门编号&#xff1b; 注意的是要查的是姓名&#xff0c;编号和部门编号 范围限定的是…

并发编程的原子性 != 事务ACID的原子性

△Hollis, 一个对Coding有着独特追求的人△这是Hollis的第 412 篇原创分享作者 l Hollis来源 l Hollis&#xff08;ID&#xff1a;hollischuang&#xff09;关于原子性&#xff0c;很多人在多个地方都听说过&#xff0c;大家也都背的很熟悉。在事务的ACID中&#xff0c;有原子性…

儒家思想和道家思想的三个主要差异

孔子、孟子、老子、庄子&#xff0c;这四位古代思想家被称为“中国四哲”&#xff0c;他们分别代表了儒家和道家思想。这两大思想流派&#xff0c;是数千年来中国人智慧的结晶和文化的瑰宝。01先秦儒家思想的发展&#xff0c;经过了三个阶段&#xff0c;第一阶段是孔子&#xf…

CHAPTER 7 Ansible playbook(四)

ansible-playbook7.1 roles&#xff08;角色&#xff09;7.1.1 Ansible Roles 介绍7.1.2 Roles结构7.1.3 存储和查找角色7.1.4 制作一个Role7.1.5 使用角色7.1.5.1 经典方法7.1.5.2 import_role7.1.6 如何使用Galaxy7.1 roles&#xff08;角色&#xff09; 7.1.1 Ansible Role…

windows docker安装prometheus和grafana

文章目录docker安装prometheusdocker安装grafanawindows安装windows_exporterprometheus配置新增windows_exporter的job,配置grafana导入windows模板即可出现酷炫大屏出现酷炫画面完成docker安装prometheus 拉取镜像,在D盘下创建prometheus.yml配置文件,映射到docker里面d:/se…

【pandas】教程:8-如何组合多个表格的数据

Pandas 组合多个表格的数据 本节使用的数据为 data/air_quality_no2_long.csv&#xff0c;链接为 pandas案例和教程所使用的数据-机器学习文档类资源-CSDN文库 导入数据 NO2NO_2NO2​ import pandas as pd air_quality_no2 pd.read_csv("data/air_quality_no2_long.cs…

二、python编程进阶02:模块和包

目录 1. python中的模块是什么 2. 导入模块: 学习import语句 2.1 import语句介绍 2.2 import导入模块的语法 2.3 导入自己的模块 2.4 导入数字开头或者带空格的模块 3. 编写自定义模块 3.1 给自定义模块编写测试代码 3.2 给自定义模块模块编写说明文档 4. 模块的搜索…

1215. 小朋友排队(树状数组应用 -- 逆序对个数)

题目如下&#xff1a; 思路 or 题解 我们可以得出交换的次数 > 逆序对个数 kkk 我们可以发现 所有 位置 左边大于它的个数 右边小于它的个数和 kik_iki​ 等于 k∗2k*2k∗2 我们可以简单证明出(感觉出)&#xff1a;答案就是 ∑1n(1ki)∗ki2\sum^n_1 \frac{(1 k_i) * k_i}…

JavaScript 错误

文章目录JavaScript 错误 - throw、try 和 catchJavaScript 错误JavaScript 抛出&#xff08;throw&#xff09;错误JavaScript try 和 catchThrow 语句实例实例JavaScript 错误 - throw、try 和 catch try 语句测试代码块的错误。 catch 语句处理错误。 throw 语句创建自定义错…