(原创)Flow数据流的使用

news/2023/12/5 15:24:18

前言

这篇文章主要介绍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.ppmy.cn/news/9650.html

相关文章

Python全栈开发(四)——python面向对象编程

前言 我们前面讲完了全栈开发的第一部分——基础&#xff0c;今天我们说说第二部分——面向对象编程。我们从什么是类&#xff0c;什么是对象&#xff0c;以及面向对象的三要素等方面来说说。 目录 前言 面向对象 面向对象的简单理解&#xff1a; 类是什么 对象是什么 对…

【华为OD机试真题 java、python、c++】机房布局【2022 Q4 200分】

代码请进行一定修改后使用,本代码保证100%通过率,本题目提供了java、python、c++三种代码 题目描述 小明正在规划一个大型数据中心机房,为了使得机柜上的机器都能正常满负荷工作,需要确保在每个机柜边上至少要有一个电箱。 为了简化题目,假设这个机房是一整排,M表示机柜…

计算机网络(物理层)

一个数据报长度为 4000 字节&#xff08;固定首部长度&#xff09;。现在经过一个网络传送&#xff0c;但此网络 能够传送的最大数据长度为 1500 字节。 试问应当划分为几个短些的数据报片&#xff1f;各数据报片的数据字段长度、片偏移字段和 MF 标志应为何数值&#xff1f; …

人脸清晰化神器codeFormer图形界面包GUI

1. codeFormer介绍 在NeurIPS 2022上&#xff0c;南洋理工大学-商汤科技联合研究中心S-Lab提出了一种基于VQGANTransformer的人脸复原模型CodeFormer&#xff0c;效果是真的强大&#xff0c;先展示下效果&#xff0c;用的官方案例效果图。 官方给出的代码见这里 传送门 2. 图…

【MySQL】1. 了解数据库以及MySQL安装

了解数据库和SQL什么是数据库为什么使用数据库MySQL下载,安装,配置客户端连接MySQL方法一方法二SQLSQL分类SQL的基本规则命名规则最后大家好, 我是路不停_。 上学期学校学习了MySQL之后,课后也没有做很多总结,加上课时紧凑,后续考试也是考前草草备考了一下. 最近读了本MySQL必知…

基础数学(八)——期末考试复习

文章目录考试要求考试内容简单复习插值拟合和回归数值积分直接法迭代法非线性方程求根微分方程数值解去年考题第一题&#xff0c;插值&#xff08;12分&#xff09;第二题&#xff0c;回归和拟合第三题&#xff0c;数值积分第四题&#xff0c;线性方程组求解第五题&#xff0c;…

linux读写自旋锁的代码分析

普通自旋锁有一些缺点&#xff1a; 对所有的竞争者不做区分。很多情况有些竞争者并不会修改共享资源普通自旋锁总是会限制只有一个内核路径持有锁 读写锁的改进&#xff1a; 允许多个读者同时持有读锁只允许一个写者同时持有写锁不允许读者和写者同时持有锁读写自旋锁更适合…

如何增加美容院客流量?

随着爱美人士越来越多&#xff0c;美容院的在各个城市都不断的壮大中&#xff0c;经常我们可以看到有些店家门庭若市&#xff0c;有些店家确实门可罗雀&#xff0c;面对这样的情况&#xff0c;作为店长如何增加美容院客流量&#xff0c;提高美容院销售业绩呢? 一、提高单笔成…

[cpp进阶]C++智能指针

文章目录为什么需要智能指针?智能指针的原理及使用智能指针的原理智能指针的使用C中的智能指针C智能指针的发展历程std::auto_ptrstd::auto_ptr的使用std::auto_ptr的模拟实现std::unique_ptrstd::unique_ptr的使用std::unique_ptr的模拟实现std::shared_ptrstd::shared_ptr的…

Rust入门(七):编写测试

Rust 中的测试函数是用来验证代码是否是按照你期望的方式运行的一类函数&#xff1a; 函数测试 Rust 中的测试就是一个带有 test 属性注解的函数&#xff0c;当使用 cargo test 命令运行测试时&#xff0c;Rust 会构建一个测试执行程序用来调用标记了 test 属性的函数&#x…

git---常用命令集合

适用平台:gitee github gerrit gitlab 提交代码和查看相关信息 git log git status git add . 增加所有修改,需要添加指定文件可以选择添加文件即可 git commit -m "xxx" git push git reset --hard commitid 保持与服务器更新到commitid git pull git diff HEAD^ …

JavaScript篇.day10-面向对象,对象,构造函数,this关键字,原型

目录面向对象对象构造函数this关键字原型面向对象面向过程: 在开发过程中,关注过程的开发方式. 在开发时关注每一个细节,步骤和顺序.面向对象: 在开发过程中,只需要找一个对象来完成事情的开发思想对象: 在生活中,万物皆对象 封装: 将完成步骤封装在对象内部属性: 对象的特征核…

JavaWeb项目 -- 博客系统

JavaWeb项目 -- 博客系统前言&#xff1a;页面展示一、创建 Maven 项目二、设计数据库三、封装数据库的操作3.1 创建 DBUtil 类3.2 创建 Blog 类3.3 创建 User 类3.4 创建类 BlogDao3.5 创建类 UserDao四、导入准备好的前端代码五、实现博客列表界面5.1 约定好前后端交互接口5.…

简单又好用的财务分析工具有哪些?

什么样的财务分析工具才能算是简单又好用&#xff1f;是能够快速完成组合多变的财务指标运算分析&#xff1b;能够充分发挥企业经营健康晴雨表作用&#xff0c;反映企业财务健康状态&#xff1b;还是能够支持多维度动态分析、自助分析&#xff1b;或者是轻松合并账套&#xff0…

Java集合类ArrayList应用 | 如何在字符串s1中删除有在字符串s2出现的字符?

目录 一、题干 二、题解 1. 思路 ArrayList实现 2. 代码 ArrayList实现 StringBuilder实现-1 StringBuilder实现-2 三、总结 一、题干 面试的编程题&#xff1a; s1: "welcome to Zhejiang" s2: "come" 要求输出从字符串s1删除s2中存在的字符之后…

【Java寒假打卡】Java基础-BigDecimal

【Java寒假打卡】Java基础-BigDecimal构造方法四则运算BigDecimal的特殊方法基本数据类型包装类自动装箱与自动拆箱Integer的类型转换将数字字符串进行拆分成整数数组构造方法 package com.hfut.edu.test1;import java.math.BigDecimal;public class test3 {public static void…

Spring AOP 面向切面编程

1.AOP是什么我们之前学过 OOP 面向对象编程, 那么 AOP 是否就比 OOP 更牛逼呢? 是否也是面向过程到面向对象这种质的跨越呢? 其实不是, 它和 OOP 思想是一种互补的关系, 是对 OOP 思想的一种补充.AOP (Aspect Oriented Programming) : 面向切面编程, 它是一种思想, 它是对某一…

3台机器配置hadoop集群_Hadoop+Hbase 分布式集群架构

安装搭建Hadoop1、 配置说明本次集群搭建共三台机器&#xff0c;具体说明下&#xff1a;主机名IP说明nn01192.168.1.51DataNode、NodeManager、ResourceManager、NameNodedn01192.168.1.52DataNode、NodeManager、SecondaryNameNodedn02192.168.1.53DataNode、NodeManager2 、安…

【二分查找】有界数组中指定下标处的最大值

题目描述 给你三个正整数 n、index 和 maxSum 。你需要构造一个同时满足下述所有条件的数组 nums&#xff08;下标 从 0 开始 计数&#xff09;&#xff1a; nums.length nnums[i] 是 正整数 &#xff0c;其中 0 < i < nabs(nums[i] - nums[i1]) < 1 &#xff0c;其…

论文投稿指南——中文核心期刊推荐(生物科学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…
最新文章