当前位置: 首页 > news >正文

Kotlin 协程与 Flow


简介
KotlinFlowKotlin 在异步编程方面的一个重要组件,它提供了一种声明式的、可组合的、基于协程的异步编程模型。Flow 的设计灵感来自于 Reactive StreamsRxJavaFlux 和其他异步编程库,但它与 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() 函数有以下几种重载形式:
  1. take(n: Int): 从流中获取前 n 个元素。
  2. takeWhile(predicate: (T) -> Boolean): 获取满足条件的元素,直到遇到第一个不满足条件的元素。
  3. 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,返回索引下标01,扁平化List后打印 01。以此类推...

除了使用 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生命周期

  1. 创建流(Flow creation):通过使用 flow { ... } 构建器或其他 Flow 构建器创建一个流。在此阶段,流是冷的,不会发射任何值。
  2. 收集流(Flow collection):通过调用 collect 函数或其他流收集操作符(如 toListfirstreduce 等)来收集流的值。在此阶段,流会开始发射值,并触发相关的操作。
  3. 流完成(Flow completion):当发射的所有值都被消费后,流会完成,并标记为完成状态。此时,流的生命周期结束。
  4. 取消流(Flow cancellation):如果收集流的代码块被取消(使用协程的 cancel 函数),或者流的收集器被销毁(如ActivityFragment 被销毁),则流的收集过程将被取消。

需要注意的是,Flow 是基于协程的,因此其生命周期与协程的生命周期密切相关。当协程被取消时,与该协程相关联的流收集也会被取消,所以在使用Flow封装网络请求时,如果想取消某个请求即把相应的协程取消即可。

先来看下onStartonCompletion

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{} 在面对以下三种情况时都会进行回调:

  1. 正常执行完毕
  2. 出现异常
  3. 被取消

异常处理

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

需要注意catchonCompletion 的执行顺序和其所处的位置有关,出现异常时谁在上游谁先执行。

上下文切换

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 是热的通道:ChannelKotlin 中用于协程之间进行通信和协同工作的机制。与 Flow 不同,Channel 是热的,即使没有接收者,它仍会持续发射数据。它可以用于多个协程之间传递数据、进行异步消息传递等情况。

区别:

  • Flow 是基于被动订阅的模型,数据的发射是由收集者的需求驱动的。每个收集者独立地订阅 Flow,可以按自己的节奏处理数据。

  • Channel 是主动推送数据的模型,数据的发送和接收是显式进行的。发送者可以将数据放入 Channel,而接收者通过调用 Channelreceive()函数主动获取数据。

适用场景

  • Flow 适合处理异步数据流,例如网络请求结果、数据库查询结果等。它提供了各种操作符(如 mapfiltertransform 等)来转换和处理数据流,同时支持背压(backpressure)处理,以避免生产者与消费者之间的压力失衡。

  • Channel 适合多个协程之间的通信和协同工作。它允许协程之间异步地发送和接收数据,可以用于实现生产者-消费者模型、事件驱动模型等。


感谢:朱涛 · Kotlin 编程第一课

由于是初学者,对协程方面见解不深,如有描述错误的地方,欢迎批评指正,不吝赐教

相关文章:

Kotlin 协程与 Flow

简介 Kotlin的Flow 是 Kotlin 在异步编程方面的一个重要组件&#xff0c;它提供了一种声明式的、可组合的、基于协程的异步编程模型。Flow 的设计灵感来自于 Reactive Streams、RxJava、Flux 和其他异步编程库&#xff0c;但它与 Kotlin 协程无缝集成&#xff0c;并提供了一种更…...

设备管理系统与物联网的融合:实现智能化设备监控和维护

在数字化时代&#xff0c;设备管理系统和物联网技术的融合为工业企业带来了巨大的变革和创新。本文将探讨设备管理系统与物联网的融合&#xff0c;重点介绍设备健康管理平台在实现智能化设备监控和维护方面的关键作用和优势。 一、设备管理系统与物联网的融合 随着物联网技术的…...

三、从官方源码精简出第1个FreeRTOS

1、官方源码下载 (1)进入FreeRTOS官网&#xff1a;FreeRTOS官网 (2)点击下载FreeRTOS。 (3)选择待示例的项目进行下载。 2、删减目录 (1)下载后解压的FreeRTOS文件如下图所示。 (2)删除下图中红框勾选的文件。 (3)删除"FreeRTOSv202212.01\FreeRTOS\Demo"目录下用…...

__call__函数的用法

__call__的用法 在 Python 中&#xff0c;类可以通过定义特殊方法 call 来使其实例对象可调用&#xff0c;就像调用普通的函数一样。当你在实例对象后面加上括号并传递参数时&#xff0c;Python 会自动调用这个对象的 call 方法。这使得你可以将类的实例对象当作函数来使用。 下…...

golang定时任务库cron实践

简介 cron一个用于管理定时任务的库&#xff0c;用 Go 实现 Linux 中crontab这个命令的效果。之前我们也介绍过一个类似的 Go 库——gron。gron代码小巧&#xff0c;用于学习是比较好的。但是它功能相对简单些&#xff0c;并且已经不维护了。如果有定时任务需求&#xff0c;还…...

Julia 流程控制

流程控制语句通过程序设定一个或多个条件语句来实现。在条件为 true 时执行指定程序代码&#xff0c;在条件为 false 时执行其他指定代码。 Julia 提供了大量的流程控制语句&#xff1a; 复合表达式&#xff1a;begin 和 ;。 条件表达式&#xff1a;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 集群负载均衡器&#xff0c;为ingress-nginx控制器提供EXTERNAL-IP&#xff0c;本示例使用metallb默认存储类&#xff0c;为gitlab p…...

ChatGPT在在线客服和呼叫中心中的应用如何?

ChatGPT在在线客服和呼叫中心领域中有广泛的应用潜力&#xff0c;可以帮助企业提供更高效、个性化和满意度更高的客户服务体验。以下是详细的讨论&#xff1a; **1. 自动化客服&#xff1a;** ChatGPT可以用于自动化客服流程&#xff0c;通过自动回复用户的常见问题和查询&…...

C++多线程环境下的单例类对象创建

使用C无锁编程实现多线程下的单例模式 贺志国 2023.8.1 在多线程环境下创建一个类的单例对象&#xff0c;要比单线程环境下要复杂很多。下面介绍在多线程环境下实现单例模式的几种方法。 一、尺寸较小的类单例对象创建 如果待创建的单例类SingletonForMultithread内包含的成…...

“深入解析JVM内部机制:从字节码到垃圾回收“

标题&#xff1a;深入解析JVM内部机制&#xff1a;从字节码到垃圾回收 摘要&#xff1a;本文将从字节码生成、类加载、运行时数据区域和垃圾回收等方面深入解析JVM的内部机制&#xff0c;并通过示例代码展示其工作原理和实践应用。 正文&#xff1a; 一、字节码生成 JVM是基…...

音频系统项目与音频算法研究方向分类

+我V hezkz17进数字音频系统研究开发交流答疑群(课题组) 音频系统项目与音频算法研究方向分类 一 音频系统项目产品分类 1 收音机,数字收音机,复读机 2 耳机,蓝牙耳机,TWS蓝牙耳机, 3 立体声音箱,AI智能音箱, 4 音频功放,车载功放, 5 音响,普通音响,Soundbar音响…...

单例模式和工厂模式

目录 今日良言&#xff1a;关关难过关关过&#xff0c;步步难行步步行 一、单例模式 1.饿汉模式 2.懒汉模式 二、工厂模式 今日良言&#xff1a;关关难过关关过&#xff0c;步步难行步步行 一、单例模式 首先来解释一下&#xff0c;什么是单例模式。 单例模式也就是单个…...

两个镜头、视野、分辨率不同的相机(rgb、红外)的视野校正

文章目录 背景实际效果查找资料资料1资料2 解决方案最终结果 背景 目前在做的项目用到两个摄像头&#xff0c;一个是热成像摄像头、另一个是普通的rgb摄像头。 一开始的目标是让他们像素级重合&#xff0c;使得点击rgb图像时&#xff0c;即可知道其像素对应的温度。但是在尝试…...

kettle 连接jdbc

DM JDBC连接 oracle JDBC连接 PG JDBC连接 SQLSERVER JDBC连接...

PyTorch中加载模型权重 A匹配B|A不匹配B

在做深度学习项目时&#xff0c;从头训练一个模型是需要大量时间和算力的&#xff0c;我们通常采用加载预训练权重的方法&#xff0c;而我们往往面临以下几种情况&#xff1a; 未修改网络&#xff0c;A与B一致 很简单&#xff0c;直接.load_state_dict() net ANet(num_cla…...

@FeignClient指定多个url实现负载均衡

C知道回答的如下&#xff1a; 在使用 FeignClient 调用多个 URL 实现负载均衡时&#xff0c;可以使用 Spring Cloud Ribbon 提供的功能来实现。下面是一个示例代码&#xff1a; 首先&#xff0c;在Spring Boot主类上添加EnableFeignClients注解启用Feign Client功能。 Spring…...

vue diff 双端比较算法

文章目录 双端指针比较策略命中策略四命中策略二命中策略三命中策略一未命中四种策略&#xff0c;遍历旧节点列表新增情况一新增情况二 删除节点双端比较的优势 双端指针 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children …...

初识React: 基础(概念 特点 高效原因 虚拟DOM JSX语法 组件)

1.什么是React? React是一个由Facebook开源的JavaScript库&#xff0c;它主要用于构建用户界面。React的特点是使用组件化的思想来构建界面&#xff0c;使得代码的可复用性和可维护性大大提高。React还引入了虚拟DOM的概念&#xff0c;减少了对真实DOM的直接操作&#xff0c;…...

自监督去噪:Neighbor2Neighbor原理分析与总结

文章目录 1. 方法原理1.1 先前方法总结1.2 Noise2Noise回顾1.3 从Noise2Noise到Neighbor2Neighbor1.4 框架结构2. 实验结果3. 总结 文章链接&#xff1a;https://arxiv.org/abs/2101.02824 参考博客&#xff1a;https://arxiv.org/abs/2101.02824 1. 方法原理 1.1 先前方法总…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录

ASP.NET Core 是一个跨平台的开源框架&#xff0c;用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录&#xff0c;以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

【Linux】shell脚本忽略错误继续执行

在 shell 脚本中&#xff0c;可以使用 set -e 命令来设置脚本在遇到错误时退出执行。如果你希望脚本忽略错误并继续执行&#xff0c;可以在脚本开头添加 set e 命令来取消该设置。 举例1 #!/bin/bash# 取消 set -e 的设置 set e# 执行命令&#xff0c;并忽略错误 rm somefile…...

FastAPI 教程:从入门到实践

FastAPI 是一个现代、快速&#xff08;高性能&#xff09;的 Web 框架&#xff0c;用于构建 API&#xff0c;支持 Python 3.6。它基于标准 Python 类型提示&#xff0c;易于学习且功能强大。以下是一个完整的 FastAPI 入门教程&#xff0c;涵盖从环境搭建到创建并运行一个简单的…...

前端导出带有合并单元格的列表

// 导出async function exportExcel(fileName "共识调整.xlsx") {// 所有数据const exportData await getAllMainData();// 表头内容let fitstTitleList [];const secondTitleList [];allColumns.value.forEach(column > {if (!column.children) {fitstTitleL…...

【解密LSTM、GRU如何解决传统RNN梯度消失问题】

解密LSTM与GRU&#xff1a;如何让RNN变得更聪明&#xff1f; 在深度学习的世界里&#xff0c;循环神经网络&#xff08;RNN&#xff09;以其卓越的序列数据处理能力广泛应用于自然语言处理、时间序列预测等领域。然而&#xff0c;传统RNN存在的一个严重问题——梯度消失&#…...

【机器视觉】单目测距——运动结构恢复

ps&#xff1a;图是随便找的&#xff0c;为了凑个封面 前言 在前面对光流法进行进一步改进&#xff0c;希望将2D光流推广至3D场景流时&#xff0c;发现2D转3D过程中存在尺度歧义问题&#xff0c;需要补全摄像头拍摄图像中缺失的深度信息&#xff0c;否则解空间不收敛&#xf…...

NFT模式:数字资产确权与链游经济系统构建

NFT模式&#xff1a;数字资产确权与链游经济系统构建 ——从技术架构到可持续生态的范式革命 一、确权技术革新&#xff1a;构建可信数字资产基石 1. 区块链底层架构的进化 跨链互操作协议&#xff1a;基于LayerZero协议实现以太坊、Solana等公链资产互通&#xff0c;通过零知…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...