Kotlin 协程与 Flow
简介
Kotlin的Flow 是 Kotlin 在异步编程方面的一个重要组件,它提供了一种声明式的、可组合的、基于协程的异步编程模型。Flow 的设计灵感来自于 Reactive Streams、RxJava、Flux 和其他异步编程库,但它与 Kotlin 协程无缝集成,并提供了一种更具 Kotlin 特性的 API。
Flow 是用于异步数据流的概念,它可以看作是一系列异步的、可并发的值或事件的流。可以通过操作符链更改、过滤、转换这些流,并且可以对这些流进行组合、合并、扁平化等操作。Flow 中的数据可以是异步的、非阻塞的,也可以是懒加载的,因此它非常适合处理类似于网络请求、数据库查询、传感器数据等异步任务。
使用示例
fun main() = runBlocking {flow {// 上游,发源地emit(111) //挂起函数emit(222)emit(333)emit(444)emit(555)}.filter { it > 200 } //过滤.map { it * 2 } //转换.take(2) //从流中获取前 2 个元素.collect {println(it)}
}//对应输出
444
666Process finished with exit code 0
看到这种链式调用是不是感觉很熟悉,没错,同RxJava一样Kotlin的Flow也分为上游和下游,emm… 也可以理解Kotlin的Flow就是为了替代RxJava,如果对RxJava有一定了解的话,学习Flow就更容易了。
应用场景
- 异步任务处理:
Flow可以方便地处理异步任务,如网络请求、数据库查询等。 - UI 事件响应:
Flow可以用于处理 UI 事件,例如按钮点击、搜索操作等。 - 数据流管道:
Flow可以作为数据处理的管道,从一个数据源源源不断地发射数据,供后续处理或展示。 - 数据流转换:
Flow可以方便地对数据进行转换、过滤、分组等操作,实现复杂的数据流处理逻辑。
下面来看一些常见的Flow使用示例,感受下Flow的魅力:
Flow 提供了各种操作符,用于转换、过滤和组合流,例如 map()、filter()、transform()、zip()、flatMapConcat() 等。这些操作符可以通过链式调用来对流进行链式操作。
Flow 还具有背压支持,可以通过 buffer()、conflate()、collectLatest() 等操作符来控制流的发送速率,从而避免生产者和消费者之间的资源不平衡问题。
1. 转换操作
map(): 将 Flow 中的每个元素转换为另一种类型。
fun createFlow(): Flow<Int> = flow {for (i in 1..5) {delay(1000)emit(i)}
}fun main() = runBlocking {createFlow().map { it * it } // 将元素平方.collect { value ->println(value) // 打印平方后的值}
}
//对应输出
1
4
9
16
25Process finished with exit code 0
-
take()函数有以下几种重载形式:
take(n: Int): 从流中获取前 n 个元素。takeWhile(predicate: (T) -> Boolean): 获取满足条件的元素,直到遇到第一个不满足条件的元素。takeLast(n: Int): 从流的末尾获取最后 n 个元素。
filter(): 根据给定的谓词函数过滤 Flow 中的元素。
fun createFlow(): Flow<Int> = flow {for (i in 1..5) {delay(1000)emit(i)}
}fun main() = runBlocking {createFlow().filter { it % 2 == 0 } // 过滤偶数.collect { value ->println(value) // 打印偶数}
}
//对应输出
2
4Process finished with exit code 0
2. 组合操作
zip(): 将两个 Flow 的元素一对一地组合在一起。
fun createFlowA(): Flow<Int> = flow {for (i in 1..5) {delay(1000)emit(i)}
}fun createFlowB(): Flow<String> = flow {for (i in 5 downTo 1) {delay(1000)emit("Item $i")}
}fun main() = runBlocking {createFlowA().zip(createFlowB()) { a, b -> "$a - $b" } // 组合 FlowA 和 FlowB 的元素.collect { value ->println(value) // 打印组合后的元素}
}
//对应输出
1 - Item 5
2 - Item 4
3 - Item 3
4 - Item 2
5 - Item 1Process finished with exit code 0
flatMapConcat(): 将 Flow 中的元素扁平化为多个 Flow,并按顺序连接起来。
fun createFlowOfList(): Flow<List<Int>> = flow {for (i in 1..3) {delay(1000)emit(List(i) { it * it }) // 发出包含整数平方的列表}
}fun main() = runBlocking {createFlowOfList().flatMapConcat { flowOfList -> flowOfList.asFlow() } // 扁平化列表中的元素.collect { value ->println(value) // 打印平方后的值}
}
//对应输出
0
0
1
0
1
4Process finished with exit code 0解释下为什么是这样的打印结果,因为上面发送了三个Flow<List<Int>>,第一个List元素个
数为1所以打印 索引的平方即只有一个元素 下表索引就是0,输出打印0的平方还是0,第二个List
元素个数为2,返回索引下标0,1,扁平化List后打印 0,1。以此类推...
除了使用 flow{} 创建 Flow 以外,还可以使用 flowOf() 这个函数
fun main() = runBlocking {flowOf(1, 2, 3, 4, 5).collect { value ->println(value) // 打印 Flow 中的元素}
}
//对应输出
1
2
3
4
5Process finished with exit code 0
flowOf 函数是用于快速创建 Flow 的便捷方式。它接受可变数量的参数,并将这些参数作为发射项放入到 Flow 中。这样,我们就可以直接在 flowOf 中指定要发射的元素,而无需使用流构建器 flow { }。
在某些场景下,我们甚至可以把 Flow 当做集合来使用,或者反过来,把集合当做 Flow 来用。
Flow.toList():
toList() 是 Kotlin Flow 中的一个终端操作符。它用于将 Flow 中的元素收集到一个列表中,并在该列表中返回。它将 Flow 中的所有元素收集起来,然后在流完成时返回一个包含所有元素的列表。
以下是 toList() 的示例用法:
fun createFlow(): Flow<Int> = flow {for (i in 1..5) {delay(1000)emit(i)}
}fun main() = runBlocking {val list: List<Int> = createFlow().toList() // 将 Flow 中的元素收集到列表中println(list) // 打印列表
}
//对应输出
[1, 2, 3, 4, 5]Process finished with exit code 0
需要注意,toList() 操作符会等待整个流完成,然后将所有元素收集到列表中。因此,如果 Flow 是一个无限流,则可能永远不会完成,或者在内存和计算资源耗尽之前无法完成。
List.asFlow():
asFlow() 是 Kotlin 标准库中 List 类的扩展函数,用于将 List 转换为 Flow。它允许将 List 中的元素作为发射项逐个发送到 Flow 中。
以下是 asFlow() 的示例用法:
fun main() = runBlocking {val list = listOf(1, 2, 3, 4, 5)list.asFlow() // 将 List 转换为 Flow.collect { value ->println(value) // 打印 Flow 中的元素}
}
//对应输出
1
2
3
4
5Process finished with exit code 0
asFlow() 的作用是将其他具有迭代性质的数据结构(如 List、Array 等)转换为 Flow,以便能够使用 Flow 的操作符和函数来处理这些数据。这对于在流式数据处理中与现有数据结构进行集成非常有用。
值得注意的是,使用 asFlow() 转换的 Flow 在发送元素时遵循迭代器的顺序。也就是说,Flow 发射的元素的顺序与原始数据结构(例如 List)中的元素顺序相同。
到目前为止可知的三种创建 Flow 的方式:
| Flow创建方式 | 适用场景 | 用法 |
|---|---|---|
| flow{} | 未知数据集 | flow { emit(getLock()) } |
| flowOf() | 已知具体的数据 | flow(1,2,3) |
| asFlow() | 数据集合 | list.asFlow() |
由上面代码示例,可以看出Flow的API总体分为三部分:上游、中间操作、下游,上游发送数据,下游接收 处理数据,其中最复杂的就是中间操作符,下面就详细介绍下Flow的中间操作符。
中间操作符
生命周期
在学习中间操作符之前,先了解下Flow的生命周期
- 创建流(Flow creation):通过使用
flow { ... }构建器或其他Flow构建器创建一个流。在此阶段,流是冷的,不会发射任何值。 - 收集流(Flow collection):通过调用
collect函数或其他流收集操作符(如toList、first、reduce等)来收集流的值。在此阶段,流会开始发射值,并触发相关的操作。 - 流完成(Flow completion):当发射的所有值都被消费后,流会完成,并标记为完成状态。此时,流的生命周期结束。
- 取消流(Flow cancellation):如果收集流的代码块被取消(使用协程的
cancel函数),或者流的收集器被销毁(如Activity或Fragment被销毁),则流的收集过程将被取消。
需要注意的是,Flow 是基于协程的,因此其生命周期与协程的生命周期密切相关。当协程被取消时,与该协程相关联的流收集也会被取消,所以在使用Flow封装网络请求时,如果想取消某个请求即把相应的协程取消即可。
先来看下onStart、onCompletion
fun main() = runBlocking {flow {emit(1)emit(2)emit(3)}.onStart {println("Flow started emitting values")}.onCompletion {println("Flow completed")}.collect { value ->println("Received value: $value")}
}
//对应输出
Flow started emitting values
Received value: 1
Received value: 2
Received value: 3
Flow completedProcess finished with exit code 0
onStart 函数允许在 Flow 开始发射元素之前执行一些操作,包括添加日志、初始化操作等。
onCompletion 函数允许在 Flow 完成之后执行一些操作,包括资源清理、收尾操作等。
并且onCompletion{} 在面对以下三种情况时都会进行回调:
- 正常执行完毕
- 出现异常
- 被取消
异常处理
Flow中的catch操作符用于捕获流中的异常,考虑到 Flow 具有上下游的特性,catch 这个操作符的作用是和它的位置强相关的即只能捕获到上游异常而无法捕获到下游异常,在使用时注意cache的位置。
看一段示例
fun main() = runBlocking {flow {emit(1)emit(2)throw NullPointerException("Null error")emit(3)}.onStart {println("Flow started emitting values")}.catch {println("Flow catch")emit(-1)}.onCompletion {println("Flow completed")}.collect { value ->println("Received value: $value")}
}
//对应输出
Flow started emitting values
Received value: 1
Received value: 2
Flow catch
Received value: -1
Flow completedProcess finished with exit code 0
需要注意catch和onCompletion 的执行顺序和其所处的位置有关,出现异常时谁在上游谁先执行。
上下文切换
Flow 非常适合复杂的异步任务。在大部分的异步任务当中,我们都需要频繁切换工作的线程。对于耗时任务,我们需要线程池当中执行,对于 UI 任务,我们需要在主线程执行。
flowOn可以完美往我们解决这一问题
fun main() = runBlocking {flow {emit(1)println("emit 1 in thread ${Thread.currentThread().name}")emit(2)println("emit 2 in thread ${Thread.currentThread().name}")emit(3)println("emit 3 in thread ${Thread.currentThread().name}")}.flowOn(Dispatchers.IO).collect {println("Collected $it in thread ${Thread.currentThread().name}")}
}
//对应输出
emit 1 in thread DefaultDispatcher-worker-2
emit 2 in thread DefaultDispatcher-worker-2
emit 3 in thread DefaultDispatcher-worker-2
Collected 1 in thread main
Collected 2 in thread main
Collected 3 in thread mainProcess finished with exit code 0
默认不使用flowOn的情况下,Flow中所有代码都是执行在主线程调度器上的,当使用flowOn切换上下文环境后,flowOn 上游代码将执行在其所指定的上下文环境中,同cache操作符一样flowOn也与其位置是强相关的。
launchIn
用于启动流的收集操作的操作符
launchIn 操作符的语法如下:
flow.launchIn(scope)
其中,flow 是待收集的流,scope 是用于启动流收集的协程作用域。
下面通过两段示例感受下launchIn的作用:
示例1:
fun main() = runBlocking {val flow = flow {emit(1)emit(2)emit(3)}val job = launch(Dispatchers.Default) {flow.collect { value ->println("Collecting $value in thread ${Thread.currentThread().name}")}}delay(1000)job.cancel()
}
示例2:
fun main() = runBlocking(Dispatchers.Default) {val flow = flow {emit(1)emit(2)emit(3)}flow.flowOn(Dispatchers.IO).onEach {println("Flow onEach $it in thread ${Thread.currentThread().name}")}.launchIn(this)delay(1000)
}
launchIn源码
public fun <T> Flow<T>.launchIn(scope: CoroutineScope): Job = scope.launch {collect() // tail-call
}
上面代码中的onEach操作符的作用是对流中的每个元素进行处理,而不改变流中的元素。它类似于其他编程语言中的 forEach 或 map 操作,但 onEach 不会返回修改后的流,而是继续返回原始流。
由于launchIn调用了collect(),所以它也是一个终止操作符,上面两种方法都可以切换collect中的上下文环境,看起来是感觉怪怪的哈,因为是在协程作用域中使用withContext{}不更方便吗?不过launchIn更大的作用是让其他操作符如 collect{}、filter{}等都运行在指定上下文环境中。
如果上面两个示例不好理解在看下这两个
示例1:
fun main() = runBlocking {val scope = CoroutineScope(Dispatchers.IO)val flow = flow {emit(1)println("Flow emit 1 in thread ${Thread.currentThread().name}")emit(2)println("Flow emit 2 in thread ${Thread.currentThread().name}")emit(3)println("Flow emit 3 in thread ${Thread.currentThread().name}")}flow.filter {println("Flow filter in thread ${Thread.currentThread().name}")it > 1}.onEach {println("Flow onEach $it in thread ${Thread.currentThread().name}")}.collect()delay(1000)
}
//对应输出
Flow filter in thread main
Flow emit 1 in thread main
Flow filter in thread main
Flow onEach 2 in thread main
Flow emit 2 in thread main
Flow filter in thread main
Flow onEach 3 in thread main
Flow emit 3 in thread mainProcess finished with exit code 0
示例2:
//只是把上面collect 换成了launchIn(scope)flow.filter {println("Flow filter in thread ${Thread.currentThread().name}")it > 1}.onEach {println("Flow onEach $it in thread ${Thread.currentThread().name}")}.launchIn(scope)
//对应输出
Flow filter in thread DefaultDispatcher-worker-1
Flow emit 1 in thread DefaultDispatcher-worker-1
Flow filter in thread DefaultDispatcher-worker-1
Flow onEach 2 in thread DefaultDispatcher-worker-1
Flow emit 2 in thread DefaultDispatcher-worker-1
Flow filter in thread DefaultDispatcher-worker-1
Flow onEach 3 in thread DefaultDispatcher-worker-1
Flow emit 3 in thread DefaultDispatcher-worker-1Process finished with exit code 0
这就一目了然了吧
需要注意:由于 Flow 当中直接使用 withContext 是很容易引发其他问题的,因此,withContext 在 Flow 当中是不被推荐的,即使要用,也应该谨慎再谨慎。
终止操作符
Flow中的终止操作符包括下面几种
collect: 收集流中的元素并执行相应操作。toList,toSet: 将流收集为列表或集合。reduce,fold: 使用给定的累加器函数将流中的元素合并为单个值。
需要注意终止操作符后面不能再点出来其他操作符,只能是Flow的最后一个操作符!
Flow为什么被称为 “冷” 的?与Channel 的区别是什么?
Flow 被称为“冷”的主要原因是它是一种惰性的数据流。冷流意味着当没有收集者订阅该流时,它是不会产生任何数据的。Flow 的执行是由收集者的需求来驱动的,只有当有一个或多个收集者订阅了 Flow,并调用了 collect 等收集操作时,Flow 才会开始发射数据。
Flow与Channel 特性:
-
Flow是惰性的数据流:Flow是一种基于协程的异步数据流处理库,在Kotlin中引入了响应式编程的思想。与其他响应式流框架(如RxJava)相比,Flow是惰性的,只有在有收集者订阅时才会开始发射数据。这使得Flow非常适合处理潜在的无限序列或需要异步处理的大量数据。 -
Channel是热的通道:Channel是Kotlin中用于协程之间进行通信和协同工作的机制。与Flow不同,Channel是热的,即使没有接收者,它仍会持续发射数据。它可以用于多个协程之间传递数据、进行异步消息传递等情况。
区别:
-
Flow是基于被动订阅的模型,数据的发射是由收集者的需求驱动的。每个收集者独立地订阅Flow,可以按自己的节奏处理数据。 -
Channel是主动推送数据的模型,数据的发送和接收是显式进行的。发送者可以将数据放入Channel,而接收者通过调用Channel的receive()函数主动获取数据。
适用场景
-
Flow适合处理异步数据流,例如网络请求结果、数据库查询结果等。它提供了各种操作符(如map、filter、transform等)来转换和处理数据流,同时支持背压(backpressure)处理,以避免生产者与消费者之间的压力失衡。 -
Channel适合多个协程之间的通信和协同工作。它允许协程之间异步地发送和接收数据,可以用于实现生产者-消费者模型、事件驱动模型等。
感谢:朱涛 · Kotlin 编程第一课
由于是初学者,对协程方面见解不深,如有描述错误的地方,欢迎批评指正,不吝赐教
相关文章:
Kotlin 协程与 Flow
简介 Kotlin的Flow 是 Kotlin 在异步编程方面的一个重要组件,它提供了一种声明式的、可组合的、基于协程的异步编程模型。Flow 的设计灵感来自于 Reactive Streams、RxJava、Flux 和其他异步编程库,但它与 Kotlin 协程无缝集成,并提供了一种更…...
设备管理系统与物联网的融合:实现智能化设备监控和维护
在数字化时代,设备管理系统和物联网技术的融合为工业企业带来了巨大的变革和创新。本文将探讨设备管理系统与物联网的融合,重点介绍设备健康管理平台在实现智能化设备监控和维护方面的关键作用和优势。 一、设备管理系统与物联网的融合 随着物联网技术的…...
三、从官方源码精简出第1个FreeRTOS
1、官方源码下载 (1)进入FreeRTOS官网:FreeRTOS官网 (2)点击下载FreeRTOS。 (3)选择待示例的项目进行下载。 2、删减目录 (1)下载后解压的FreeRTOS文件如下图所示。 (2)删除下图中红框勾选的文件。 (3)删除"FreeRTOSv202212.01\FreeRTOS\Demo"目录下用…...
__call__函数的用法
__call__的用法 在 Python 中,类可以通过定义特殊方法 call 来使其实例对象可调用,就像调用普通的函数一样。当你在实例对象后面加上括号并传递参数时,Python 会自动调用这个对象的 call 方法。这使得你可以将类的实例对象当作函数来使用。 下…...
golang定时任务库cron实践
简介 cron一个用于管理定时任务的库,用 Go 实现 Linux 中crontab这个命令的效果。之前我们也介绍过一个类似的 Go 库——gron。gron代码小巧,用于学习是比较好的。但是它功能相对简单些,并且已经不维护了。如果有定时任务需求,还…...
Julia 流程控制
流程控制语句通过程序设定一个或多个条件语句来实现。在条件为 true 时执行指定程序代码,在条件为 false 时执行其他指定代码。 Julia 提供了大量的流程控制语句: 复合表达式:begin 和 ;。 条件表达式:if-elseif-else 和 ?: (三…...
问题解决方案
前端开发 1、npm安装的时候老是卡住 reify:rxjs: timing reifyNode:node_modules/vue/cli/node_modules 查看当前使用的那个镜像 nrm lsnpm ---------- https://registry.npmjs.org/yarn --------- https://registry.yarnpkg.com/cnpm --------- https://r.cnpmjs.org/taobao …...
kubernetes基于helm部署gitlab-operator
kubernetes基于helm部署gitlab-operator 这篇博文介绍如何在 Kubernetes 中使用helm部署 GitLab-operator。 先决条件 已运行的 Kubernetes 集群负载均衡器,为ingress-nginx控制器提供EXTERNAL-IP,本示例使用metallb默认存储类,为gitlab p…...
ChatGPT在在线客服和呼叫中心中的应用如何?
ChatGPT在在线客服和呼叫中心领域中有广泛的应用潜力,可以帮助企业提供更高效、个性化和满意度更高的客户服务体验。以下是详细的讨论: **1. 自动化客服:** ChatGPT可以用于自动化客服流程,通过自动回复用户的常见问题和查询&…...
C++多线程环境下的单例类对象创建
使用C无锁编程实现多线程下的单例模式 贺志国 2023.8.1 在多线程环境下创建一个类的单例对象,要比单线程环境下要复杂很多。下面介绍在多线程环境下实现单例模式的几种方法。 一、尺寸较小的类单例对象创建 如果待创建的单例类SingletonForMultithread内包含的成…...
“深入解析JVM内部机制:从字节码到垃圾回收“
标题:深入解析JVM内部机制:从字节码到垃圾回收 摘要:本文将从字节码生成、类加载、运行时数据区域和垃圾回收等方面深入解析JVM的内部机制,并通过示例代码展示其工作原理和实践应用。 正文: 一、字节码生成 JVM是基…...
音频系统项目与音频算法研究方向分类
+我V hezkz17进数字音频系统研究开发交流答疑群(课题组) 音频系统项目与音频算法研究方向分类 一 音频系统项目产品分类 1 收音机,数字收音机,复读机 2 耳机,蓝牙耳机,TWS蓝牙耳机, 3 立体声音箱,AI智能音箱, 4 音频功放,车载功放, 5 音响,普通音响,Soundbar音响…...
单例模式和工厂模式
目录 今日良言:关关难过关关过,步步难行步步行 一、单例模式 1.饿汉模式 2.懒汉模式 二、工厂模式 今日良言:关关难过关关过,步步难行步步行 一、单例模式 首先来解释一下,什么是单例模式。 单例模式也就是单个…...
两个镜头、视野、分辨率不同的相机(rgb、红外)的视野校正
文章目录 背景实际效果查找资料资料1资料2 解决方案最终结果 背景 目前在做的项目用到两个摄像头,一个是热成像摄像头、另一个是普通的rgb摄像头。 一开始的目标是让他们像素级重合,使得点击rgb图像时,即可知道其像素对应的温度。但是在尝试…...
kettle 连接jdbc
DM JDBC连接 oracle JDBC连接 PG JDBC连接 SQLSERVER JDBC连接...
PyTorch中加载模型权重 A匹配B|A不匹配B
在做深度学习项目时,从头训练一个模型是需要大量时间和算力的,我们通常采用加载预训练权重的方法,而我们往往面临以下几种情况: 未修改网络,A与B一致 很简单,直接.load_state_dict() net ANet(num_cla…...
@FeignClient指定多个url实现负载均衡
C知道回答的如下: 在使用 FeignClient 调用多个 URL 实现负载均衡时,可以使用 Spring Cloud Ribbon 提供的功能来实现。下面是一个示例代码: 首先,在Spring Boot主类上添加EnableFeignClients注解启用Feign Client功能。 Spring…...
vue diff 双端比较算法
文章目录 双端指针比较策略命中策略四命中策略二命中策略三命中策略一未命中四种策略,遍历旧节点列表新增情况一新增情况二 删除节点双端比较的优势 双端指针 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children …...
初识React: 基础(概念 特点 高效原因 虚拟DOM JSX语法 组件)
1.什么是React? React是一个由Facebook开源的JavaScript库,它主要用于构建用户界面。React的特点是使用组件化的思想来构建界面,使得代码的可复用性和可维护性大大提高。React还引入了虚拟DOM的概念,减少了对真实DOM的直接操作,…...
自监督去噪:Neighbor2Neighbor原理分析与总结
文章目录 1. 方法原理1.1 先前方法总结1.2 Noise2Noise回顾1.3 从Noise2Noise到Neighbor2Neighbor1.4 框架结构2. 实验结果3. 总结 文章链接:https://arxiv.org/abs/2101.02824 参考博客:https://arxiv.org/abs/2101.02824 1. 方法原理 1.1 先前方法总…...
业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...
SkyWalking 10.2.0 SWCK 配置过程
SkyWalking 10.2.0 & SWCK 配置过程 skywalking oap-server & ui 使用Docker安装在K8S集群以外,K8S集群中的微服务使用initContainer按命名空间将skywalking-java-agent注入到业务容器中。 SWCK有整套的解决方案,全安装在K8S群集中。 具体可参…...
椭圆曲线密码学(ECC)
一、ECC算法概述 椭圆曲线密码学(Elliptic Curve Cryptography)是基于椭圆曲线数学理论的公钥密码系统,由Neal Koblitz和Victor Miller在1985年独立提出。相比RSA,ECC在相同安全强度下密钥更短(256位ECC ≈ 3072位RSA…...
PL0语法,分析器实现!
简介 PL/0 是一种简单的编程语言,通常用于教学编译原理。它的语法结构清晰,功能包括常量定义、变量声明、过程(子程序)定义以及基本的控制结构(如条件语句和循环语句)。 PL/0 语法规范 PL/0 是一种教学用的小型编程语言,由 Niklaus Wirth 设计,用于展示编译原理的核…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
【论文阅读28】-CNN-BiLSTM-Attention-(2024)
本文把滑坡位移序列拆开、筛优质因子,再用 CNN-BiLSTM-Attention 来动态预测每个子序列,最后重构出总位移,预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵(S…...
网站指纹识别
网站指纹识别 网站的最基本组成:服务器(操作系统)、中间件(web容器)、脚本语言、数据厍 为什么要了解这些?举个例子:发现了一个文件读取漏洞,我们需要读/etc/passwd,如…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
Python 高效图像帧提取与视频编码:实战指南
Python 高效图像帧提取与视频编码:实战指南 在音视频处理领域,图像帧提取与视频编码是基础但极具挑战性的任务。Python 结合强大的第三方库(如 OpenCV、FFmpeg、PyAV),可以高效处理视频流,实现快速帧提取、压缩编码等关键功能。本文将深入介绍如何优化这些流程,提高处理…...
Modbus RTU与Modbus TCP详解指南
目录 1. Modbus协议基础 1.1 什么是Modbus? 1.2 Modbus协议历史 1.3 Modbus协议族 1.4 Modbus通信模型 🎭 主从架构 🔄 请求响应模式 2. Modbus RTU详解 2.1 RTU是什么? 2.2 RTU物理层 🔌 连接方式 ⚡ 通信参数 2.3 RTU数据帧格式 📦 帧结构详解 🔍…...
