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 先前方法总…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
盘古信息PCB行业解决方案:以全域场景重构,激活智造新未来
一、破局:PCB行业的时代之问 在数字经济蓬勃发展的浪潮中,PCB(印制电路板)作为 “电子产品之母”,其重要性愈发凸显。随着 5G、人工智能等新兴技术的加速渗透,PCB行业面临着前所未有的挑战与机遇。产品迭代…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
基于服务器使用 apt 安装、配置 Nginx
🧾 一、查看可安装的 Nginx 版本 首先,你可以运行以下命令查看可用版本: apt-cache madison nginx-core输出示例: nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
第25节 Node.js 断言测试
Node.js的assert模块主要用于编写程序的单元测试时使用,通过断言可以提早发现和排查出错误。 稳定性: 5 - 锁定 这个模块可用于应用的单元测试,通过 require(assert) 可以使用这个模块。 assert.fail(actual, expected, message, operator) 使用参数…...
uniapp微信小程序视频实时流+pc端预览方案
方案类型技术实现是否免费优点缺点适用场景延迟范围开发复杂度WebSocket图片帧定时拍照Base64传输✅ 完全免费无需服务器 纯前端实现高延迟高流量 帧率极低个人demo测试 超低频监控500ms-2s⭐⭐RTMP推流TRTC/即构SDK推流❌ 付费方案 (部分有免费额度&#x…...
数据库分批入库
今天在工作中,遇到一个问题,就是分批查询的时候,由于批次过大导致出现了一些问题,一下是问题描述和解决方案: 示例: // 假设已有数据列表 dataList 和 PreparedStatement pstmt int batchSize 1000; // …...
