Android学习总结之Kotlin 协程
一、引言
在 Android 开发中,异步任务处理是绕不开的话题。传统的线程、Handler、AsyncTask 等方案要么过于繁琐,要么存在生命周期管理问题。Kotlin 协程的出现,以优雅的语法和强大的结构化并发能力,成为解决异步编程难题的理想方案。本文将结合核心概念、使用场景和实战经验,带您全面掌握协程的精髓。
二、协程核心概念:轻量级的异步处理
首先要明确的是,协程不是线程。线程是操作系统调度的基本单位,由操作系统负责管理和调度。而协程是在程序层面实现的一种并发编程机制,由协程库(如 kotlinx.coroutines
)进行管理。
1. 什么是协程?
- 定义:协程是基于线程的轻量级 “线程”,通过
suspend
挂起函数实现异步逻辑同步化。核心特点是可挂起恢复,避免阻塞主线程。 - 优势:比线程更轻量(一个线程可运行多个协程),通过调度器灵活切换线程,确保 UI 线程安全。
2. 挂起函数(suspend function)
- 定义:用
suspend
关键字修饰的函数,只能在协程体内或其他挂起函数中调用。 - 核心作用:在不阻塞线程的前提下暂停执行,例如
delay(1000)
、网络请求等耗时操作。
3. 挂起 vs 阻塞
- 挂起:不阻塞主线程,UI 可正常刷新(如
withContext(Dispatchers.IO)
处理耗时任务)。 - 阻塞:导致主线程 ANR(如直接在 UI 线程执行磁盘读写)。
协程的挂起和恢复的工作原理(Continuation)
1. CPS 转换
Java 中没有 suspend
函数,suspend
是 Kotlin 特有的关键字。在编译时,Kotlin 编译器会对含有 suspend
关键字的函数进行转换,这种转换在 Kotlin 中被称为 CPS 转换(continuation - passing - style)。
例如,程序员编写的挂起函数代码:
suspend fun getUserInfo(): User {val user = User("asd123", "userName", "nickName")return user
}
经过假想的中间态转换后(便于理解):
fun getUserInfo(callback: Callback<User>): Any? {val user = User("asd123", "userName", "nickName")callback.onSuccess(user)return Unit
}
最终转换后的代码:
fun getUserInfo(cont: Continuation<User>): Any? {val user = User("asd123", "userName", "nickName")cont.resume(user)return Unit
}
通过 Kotlin 生成字节码工具查看字节码并反编译成 Java 代码,也能验证确实会引入一个 Continuation
对象来实现恢复流程,这个 Continuation
对象包含了 Callback
的形态。它有两个重要作用:暂停并记住执行点位;记住函数暂停时刻的局部变量上下文。这就是我们可以用同步方式写异步代码的原因,因为 Continuation
帮我们完成了回调流程。
2. Continuation 源码分析
以下是 Continuation
源码的部分内容:
internal abstract class BaseContinuationImpl() : Continuation<Any?> {public final override fun resumeWith(result: Result<Any?>) {// 省略好多代码invokeSuspend()// 省略好多代码}protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}internal abstract class ContinuationImpl(completion: Continuation<Any?>?,private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {protected abstract fun invokeSuspend(result: Result<Any?>): Any?
}
3. 状态机原理
以一个包含多个挂起函数的协程代码为例:
suspend fun testCoroutine() {val user = apiService.getUserInfoSuspend() // 挂起函数 IO 线程tvNickName.text = user?.nickName // UI 线程更新界面val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) // 挂起函数 IO 线程tvMsgCount.text = unReadMsgCount.toString() // UI 线程更新界面
}
经过 Kotlin 编译器编译后:
fun testCoroutine(completion: Continuation<Any?>): Any? {class TestContinuation(completion: Continuation<Any?>?) : ContinuationImpl(completion) {var label: Int = 0lateinit var user: Anylateinit var unReadMsgCount: Intvar result = continuation.resultvar suspendReturn: Any? = nullval sFlag = CoroutineSingletons.COROUTINE_SUSPENDEDoverride fun invokeSuspend(_result: Result<Any?>): Any? {result = _resultlabel = label or Int.Companion.MIN_VALUEreturn testCoroutine(this)}}val continuation = if (completion is TestContinuation) {completion} else {TestContinuation(completion)}var loop = truewhile (loop) {when (continuation.label) {0 -> {throwOnFailure(result)continuation.label = 1suspendReturn = getUserInfoSuspend(continuation)if (suspendReturn == sFlag) {return suspendReturn} else {result = suspendReturn}}1 -> {throwOnFailure(result)user = result as Anycontinuation.label = 2suspendReturn = getUnReadMsgCountSuspend(user.token, continuation)if (suspendReturn == sFlag) {return suspendReturn} else {result = suspendReturn}}2 -> {throwOnFailure(result)user = continuation.mUser as AnyunReadMsgCount = continuation.unReadMsgCount as Intloop = false}}}
}
通过一个 label
标签控制分支代码的执行。当 label
为 0 时,首先将 label
设置为下一个分支的数值,然后执行第一个 suspend
方法并传递当前 Continuation
,得到返回值。如果返回值是 COROUTINE_SUSPENDED
,协程框架就直接返回,协程挂起。当第一个 suspend
方法执行完成后,会回调 Continuation
的 invokeSuspend
方法,进入第二个分支执行,以此类推,直到执行完所有 suspend
方法。每一个挂起点和初始挂起点对应的 Continuation
都会转化为一种状态,协程恢复只是跳转到下一种状态中。挂起函数将执行过程分为多个 Continuation
片段,并且利用状态机的方式保证各个片段是顺序执行的。
综上所述,协程的挂起和恢复的本质是 CPS + 状态机。
(一)内存占用
1. 线程的内存占用
每个线程在创建时都会分配一定数量的栈内存,默认情况下大约为 1MB。当系统需要启动大量线程时,会消耗大量的内存资源,这可能导致系统资源枯竭,影响系统的稳定性和性能。
2. 协程的内存占用
协程运行在现有的线程中,它们不需要单独的栈内存,而是共享调用栈。这使得协程的内存开销非常小,通常每个协程只占用几个 KB。这种特性使得同一线程可以管理和运行大量的协程,不受传统线程数量的限制。
(二)任务切换
1. 线程切换
线程切换由操作系统管理,涉及到用户态和内核态之间的切换。这个过程代价较高,需要保存和恢复 CPU 寄存器、程序计数器、内存栈等信息。频繁的线程切换会带来显著的性能开销。
2. 协程切换
协程切换在用户态完成,不涉及内核态切换,只是切换函数的上下文。与线程切换相比,协程切换的代价相对低很多,能够提高程序的执行效率。
(一)线程内存模型
在 JVM 中,每个线程都有自己独立的线程栈,每个栈帧包含局部变量、操作数栈和相关信息。当线程被挂起时,所有这些信息必须保存并在重新调度时恢复,这涉及到较多的内存操作和上下文切换。
(二)协程内存模型
协程的栈帧通常是堆上的对象。当协程挂起时,不需要切换线程,只是函数调用的上下文发生变化,协程状态会被保存到堆中。这种模型使得内存利用更加高效和灵活。
(三)协程堆栈帧
协程在挂起时,会将当前的堆栈帧转换为对象并存储在堆中。这个对象包含了所有当前帧的局部变量、挂起点以及其他必要信息。当协程恢复执行时,这个对象会重新转换为堆栈帧并继续执行。
(四)Continuation
Kotlin 中的挂起函数实质上会被编译器转换成带有回调的 Continuation
对象。该对象包含两个主要部分:
- 上下文(Continuation Context):绑定的执行环境。
- 恢复逻辑(Resume Logic):保存和处理挂起点的逻辑。
以下是 Continuation
接口的定义:
interface Continuation<in T> {val context: CoroutineContextfun resumeWith(result: Result<T>)
}
三、协程调度器:线程切换的指挥官
协程通过Dispatchers
指定运行线程,常见调度器:
调度器 | 用途 | 示例场景 |
---|---|---|
Dispatchers.Main | 主线程,处理 UI 更新、LiveData 通知 | 更新 TextView 文本 |
Dispatchers.IO | 非主线程,优化磁盘 / 网络 IO | 文件读写、Retrofit 网络请求 |
Dispatchers.Default | 非主线程,CPU 密集型任务 | JSON 解析、数据排序 |
Dispatchers.Unconfined | 不受限调度(不推荐) | 测试或简单异步逻辑 |
最佳实践:耗时操作(IO/CPU)务必切换到非主线程调度器,避免阻塞 UI。
四、作用域与生命周期:避免协程泄漏的关键
1. 结构化并发:协程的 “作用域牢笼”
- 协程必须在作用域(
CoroutineScope
)内启动,作用域负责追踪和取消子协程,避免内存泄漏。 - 核心原则:协程的生命周期绑定到创建它的组件(如 Activity、ViewModel)。
2. 常用作用域
作用域 | 生命周期绑定 | 使用场景 | 取消时机 |
---|---|---|---|
GlobalScope | 进程级(危险!) | 全局监听(极少使用) | 进程结束时自动取消 |
MainScope() | Activity/Fragment | onCreate 中启动协程 | onDestroy 时自动取消 |
ViewModelScope | ViewModel | ViewModel 内异步数据加载 | ViewModel 销毁时取消 |
lifecycleScope | Activity/Fragment | AndroidX 组件专用 | 组件生命周期结束时取消 |
示例:在 Activity 中使用lifecycleScope
button.setOnClickListener {lifecycleScope.launch(Dispatchers.IO) {// 耗时操作withContext(Dispatchers.Main) { textView.text = "加载完成" }}
}
3. 手动创建作用域:CoroutineScope()
// 自定义作用域,手动管理取消
val myScope = CoroutineScope(Dispatchers.IO + Job())
// 启动协程
val job = myScope.launch { ... }
// 取消作用域
myScope.cancel()
五、协程构建器:启动协程的 “工具箱”
1. 核心构建器对比
构建器 | 阻塞当前线程? | 返回值 | 执行模式 | 用途 |
---|---|---|---|---|
launch | 否 | Job (无返回值) | 非阻塞异步 | 启动无需返回值的协程 |
async | 否 | Deferred<T> (可获取结果) | 非阻塞异步 | 启动需要返回值的协程 |
runBlocking | 是(测试专用) | Unit | 阻塞当前线程 | 测试或简单协程入口 |
withContext | 否 | 指定类型结果 | 串行切换线程 | 切换调度器并返回结果 |
2. 高级用法:启动模式
LAZY
:延迟启动,调用start()
或await()
时才执行(适合按需加载)。
val deferred = async(start = CoroutineStart.LAZY) { loadData() }
// 手动启动
deferred.start()
ATOMIC
:创建后立即调度,首个挂起前不响应取消。UNDISPATCHED
:在当前线程执行直到首个挂起点(用于性能优化)。
(一)异步场景示例
以一个常见的异步场景为例:
- 第一步:通过接口获取当前用户的 token 及用户信息。
- 第二步:将用户的昵称展示在界面上。
- 第三步:利用获取到的 token 获取当前用户的消息未读数。
- 第四步:将未读数展示在界面上。
(二)现有方案实现及问题
以下是使用现有方案(回调函数)实现上述异步场景的代码:
apiService.getUserInfo().enqueue(object : Callback<User> {override fun onResponse(call: Call<User>, response: Response<User>) {val user = response.body()tvNickName.text = user?.nickNameapiService.getUnReadMsgCount(user?.token).enqueue(object : Callback<Int> {override fun onResponse(call: Call<Int>, response: Response<Int>) {val tvUnReadMsgCount = response.body()tvMsgCount.text = tvUnReadMsgCount.toString()}})}
})
可以看到,当需要处理多个异步任务且存在嵌套关系时,代码会变得复杂,形成所谓的「回调地狱」(callback hell),这会降低代码的可读性和可维护性。
(三)协程实现及优势
使用协程实现上述异步场景的代码如下:
mainScope.launch {val user = apiService.getUserInfoSuspend() // IO 线程请求数据tvNickName.text = user?.nickName // UI 线程更新界面val unReadMsgCount = apiService.getUnReadMsgCountSuspend(user?.token) // IO 线程请求数据tvMsgCount.text = unReadMsgCount.toString() // UI 线程更新界面
}suspend fun getUserInfoSuspend(): User? {return withContext(Dispatchers.IO) {// 模拟网络请求耗时操作delay(10)User("asd123", "userName", "nickName")}
}suspend fun getUnReadMsgCountSuspend(token: String?): Int {return withContext(Dispatchers.IO) {// 模拟网络请求耗时操作delay(10)10}
}
在协程实现中,告别了回调函数,避免了回调地狱的问题。协程最大的优势在于可以让我们用同步的代码写出异步的效果,也就是将异步代码同步去写。同时,协程还能让程序员更方便地处理异步业务,更便捷地切换线程,保证主线程的安全。
六、并发处理:并行与串行的选择
1. 并行执行:async
+ await
// 并发获取数据(耗时1s)
val time = measureTimeMillis {val one = async { doOne() } // 耗时1sval two = async { doTwo() } // 耗时1sprintln(one.await() + two.await()) // 30
}
println("耗时:$time ms") // 约1000ms
2. 串行执行:withContext
或 无async
// 串行执行(耗时2s)
val time = measureTimeMillis {val one = doOne() // 耗时1sval two = doTwo() // 耗时1sprintln(one + two) // 30
}
println("耗时:$time ms") // 约2000ms
扩展追问:
概述:
进程是程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位;线程是进程中的一个执行单元,是 CPU 调度和分派的基本单位;而协程是一种比线程更加轻量级的并发编程方式,它可以在一个线程中实现多个任务的并发执行。
进程、线程、协程对比表
维度 | 进程(Process) | 线程(Thread) | 协程(Coroutine) |
---|---|---|---|
定义 | 程序在操作系统中的一次执行实例,是资源分配的基本单位。 | 进程内的执行单元,是 CPU 调度的基本单位。 | 用户态轻量级 “线程”,由协程库管理,可在同一线程内协作式调度。 |
调度单位 | 操作系统内核调度 | 操作系统内核调度 | 协程库(用户态调度,无需内核参与) |
上下文切换 | 内核态切换,开销极大(涉及内存地址空间、文件描述符等)。 | 内核态切换,开销较大(涉及寄存器、栈指针等)。 | 用户态切换,开销极小(仅保存协程状态到堆,无需内核干预)。 |
内存占用 | 独立地址空间(通常数 MB 到 GB 级)。 | 共享进程内存空间,每个线程栈默认约 1 MB。 | 共享线程内存,每个协程仅需几个 KB(无独立栈,共享调用栈)。 |
并发性 | 进程间并发,由操作系统控制。 | 线程间并发,由操作系统控制(抢占式多任务)。 | 协程间并发,由协程库控制(协作式多任务,需主动挂起)。 |
创建开销 | 高(需分配独立内存、文件句柄等资源)。 | 中(需分配线程栈、寄存器上下文)。 | 极低(仅创建协程对象,复用线程资源)。 |
切换开销 | 最高(涉及内核态上下文和地址空间切换)。 | 较高(内核态线程上下文切换)。 | 最低(用户态协程状态保存 / 恢复,无内核参与)。 |
资源隔离 | 完全隔离(地址空间、文件描述符等)。 | 部分隔离(共享进程内存,独立栈、寄存器)。 | 不隔离(共享线程内存,通过协程作用域管理生命周期)。 |
执行控制权 | 操作系统完全控制(不可预测抢占)。 | 操作系统完全控制(不可预测抢占)。 | 协程主动控制(通过 suspend 挂起,协作式恢复)。 |
典型用途 | 独立程序运行(如浏览器、IDE 等独立进程)。 | 多任务处理(如网络请求、文件读写等异步操作)。 | 高并发轻量任务(如海量 I/O 操作、事件驱动逻辑)。 |
代表技术 | Linux 进程、Windows 进程。 | Java 线程、POSIX 线程(pthread)。 | Kotlin 协程、Go Goroutine、Python asyncio 协程。 |
生命周期 | 由操作系统管理(创建 / 销毁开销大)。 | 由操作系统管理(依赖进程生命周期)。 | 由协程库管理(绑定作用域,如 Android 的 lifecycleScope )。 |
上下文保存位置 | 硬盘或内存(进程切换时保存完整状态)。 | 内存(线程栈和寄存器状态)。 | 堆(协程状态封装为 Continuation 对象)。 |
阻塞影响 | 进程阻塞不影响其他进程。 | 线程阻塞会占用 CPU 时间片,影响同进程内其他线程。 | 协程阻塞不阻塞线程,可释放线程执行其他协程。 |
核心差异总结
-
调度层级:
- 进程和线程由 操作系统内核 调度,属于 内核态并发;
- 协程由 协程库 调度,属于 用户态并发,依赖于线程但更轻量。
-
资源开销:
- 进程:资源隔离性最强,但创建和切换开销最大;
- 线程:共享进程资源,开销中等;
- 协程:几乎无额外资源开销,可在单线程内运行数万个协程。
-
控制方式:
- 进程 / 线程:由操作系统强制抢占,不可预测;
- 协程:通过
suspend
主动挂起,协作式恢复,适合细粒度异步控制。
-
适用场景:
- 进程:适合完全隔离的独立任务;
- 线程:适合 CPU 密集型或需要系统级并发的任务;
- 协程:适合 I/O 密集型、高并发轻量任务(如网络请求、UI 异步更新)。
总结
Kotlin 协程之所以被称为 “轻量级线程”,主要是因为它具有以下优点:
- 内存占用更少:协程不需要独立的栈内存,而是共享调用栈,大大减少了内存开销。
- 低切换开销:协程切换在用户态完成,无需与操作系统交互,切换代价小。
- 高并发模型:在同一线程上可以高效地运行大量协程,不受传统线程创建和管理的限制。
通过使用协程,开发者可以更高效地处理并发任务,提高程序的性能和可维护性。
相关文章:
Android学习总结之Kotlin 协程
一、引言 在 Android 开发中,异步任务处理是绕不开的话题。传统的线程、Handler、AsyncTask 等方案要么过于繁琐,要么存在生命周期管理问题。Kotlin 协程的出现,以优雅的语法和强大的结构化并发能力,成为解决异步编程难题的理想方…...
docker的与使用
1 docker初体验 1.1 docker简介 问题:为什么会有docker出现? 一款产品从开发到上线,从操作系统,到运行环境,再到应用配置。作为开发运维之间的协作我们需要关心很多东西,这也是很多互联网公司都不得不面对…...
解决ubuntu18.04无法进入系统桌面
解决ubuntu18.04无法进入系统桌面 解决ubuntu18.04无法进入系统桌面前言1、原因2、解决现象总结 前言 Vmware虚拟机运行跑Linux项目,没有关掉运行的进程就关机,导致系统无法进入系统桌面,一直卡在系统的初始化界面,按下快捷键发…...
Docker学习之容器虚拟化与虚拟机的区别(day11)
文章目录 前言一、问题描述二、具体内容1. 虚拟机(VM)2. 容器虚拟化(Docker)容器虚拟化的核心技术 三、总结1. 资源占用对比2. 适用场景3. 结论 前言 在现代软件开发和部署过程中,Docker 和虚拟机(VM&…...
无人机数据链技术及运行方式详解!
一、无人机数据链技术要点 1. 通信传输技术 频段选择: 常用频段包括 L波段(1-2 GHz)、C波段(4-8 GHz)、Ku/K波段(12-40 GHz),不同频段在传输距离、带宽和抗干扰性间权衡。 低…...
【JavaEE】MyBatis - Plus
目录 一、快速使用二、CRUD简单使用三、常见注解3.1 TableName3.2 TableFiled3.3 TableId 四、条件构造器4.1 QueryWrapper4.2 UpdateWrapper4.3 LambdaQueryWrapper4.4 LambdaUpdateWrapper 五、自定义SQL 一、快速使用 MyBatis Plus官方文档:MyBatis Plus官方文档…...
设计模式 三、结构型设计模式
一、代理模式 代理设计模式(Proxy Design Pattern)是一种结构型设计模式,它为其他对象提供了一个代理,以控制对这个对象的访问。 代理模式可以用于实现懒加载、安全访问控制、日志记录等功能。简单来说,代理模式 就是通…...
视频编码器的抉择:x264、x265、libaom、vvenc 对比测试实验
264、x265、libaom、vvenc 对比测试实验 测试机器配置:Apple M1 Pro -16G编码器版本(选择自己编译):所有源码都是当前最新更新的状态,此外各类编码具体的编译过程可参考我的相关系列博客。 编码器GitHubx264git clon…...
JMeter脚本录制(火狐)
录制前准备: 电脑: 1、将JMeter证书导入,(bin目录下有一个证书,需要安装这个证书到电脑中) 2、按winr,输入certmgr.msc,打开证书,点击下一步,输入JMeter证书…...
10、Linux C 网络编程(完整版)
1、网络发展历史和分层 1.1 Internet 的历史 起源: 1957 年:苏联发射第一颗人造卫星 "Sputnik"。 1958 年:美国总统艾森豪威尔成立 DARPA(国防部高级研究计划署)。 1968 年:DARPA 提出 "…...
拼多多 anti-token unidbg 分析
声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 逆向分析 版本7.3-7.4 都试过加密没什…...
Swoole 的 Hyperf 框架和 Go 的 Gin 框架高并发原理以及技术实现对比分析
Swoole 的 Hyperf 框架和 Go 的 Gin 框架虽然都支持高并发,但它们的实现原理、底层机制和适用场景有显著差异。以下从 高并发原理、技术实现区别、优缺点 三个方面详细分析: 一、高并发实现原理 1. Hyperf (PHP Swoole) Hyperf 的高并发能力基于 Swoo…...
CSS3学习教程,从入门到精通,CSS3 媒体查询实现响应式布局语法指南(21)
CSS3 媒体查询实现响应式布局语法指南 一、媒体查询核心语法 1. 基础语法结构 media 媒体类型 and (媒体特性) {/* 匹配条件时应用的CSS规则 */ }2. 媒体类型(可省略) 类型值说明all所有设备(默认值)screen屏幕设备print打印机…...
C#中,什么是委托,什么是事件及它们之间的关系
1. 委托(Delegate) 定义与作用 委托是类型安全的函数指针,用于封装方法,支持多播(链式调用)。核心能力:将方法作为参数传递或异步回调。 使用场景 回调机制(如异步操作完…...
【LeetCode 热题100】347:前 K 个高频元素(详细解析)(Go语言版)
🚀 力扣热题 347:前 K 个高频元素(详细解析) 📌 题目描述 力扣 347. 前 K 个高频元素 给你一个整数数组 nums 和一个整数 k,请你返回其中出现频率 前 k 高的元素。你可以按 任意顺序 返回答案。 …...
②EtherCAT/Ethernet/IP/Profinet/ModbusTCP协议互转工业串口网关
型号 协议转换通信网关 EtherCAT 转 Modbus TCP 配置说明 网线连接电脑到模块上的 WEB 网页设置网口,电脑所连网口的网段设置成 192.168.1.X(X 是除 8 外的任一数值)后,打开浏览器,地址栏输入 192.168.1.8 ÿ…...
微服务集成测试 -华为OD机试真题(A卷、Python)
题目描述 现在有n个容器服务,服务的启动可能有一定的依赖性(有些服务启动没有依赖),其次,服务自身启动加载会消耗一些时间。 给你一个n n 的二维矩阵useTime,其中useTime[i][i]10表示服务i自身启动加载需…...
k8s常用总结
1. Kubernetes 架构概览 主节点(Master): 负责集群管理,包括 API Server、Controller Manager、Scheduler 和 etcd 存储。 工作节点(Node): 运行 Pod 和容器,包含 kubelet、kube-pr…...
【算法】并查集基础讲解
一、定义 一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。思想是用一个数组表示了整片森林(parent),树的根节点唯一标识了一个集合,只要找到了某个元素的的树根,就能确定它在哪个集合里。 …...
探索PHP的未来发展与应用趋势
PHP,作为Web开发领域的常青树,自1995年诞生以来,始终在动态网页开发中占据重要席位。随着技术的不断演进,PHP也在持续更新,以适应现代开发需求。本文将深入探讨PHP的最新发展动态及其在2025年的应用趋势。 PHP 8&…...
C#调用ACCESS数据库,解决“Microsoft.ACE.OLEDB.12.0”未注册问题
C#调用ACCESS数据库,解决“Microsoft.ACE.OLEDB.12.0”未注册问题 解决方法: 1.将C#采用的平台从AnyCpu改成X64 2.将官网下载的“Microsoft Access 2010 数据库引擎可再发行程序包AccessDatabaseEngine_X64”文件解压 3.安装解压后的文件 点击下载安…...
ubuntu22.04.5安装docker,解决安装出现的错误,解决Docker hello-world没打印出来
文章目录 前言一 安装失败解决1结合具体报错分析2 首先怀疑是VPN的问题3 直接百度报错信息4最终解决问题 二 验证Docker hello-world没打印出来总结 前言 先说一下前面的情况,使用的是公司的工作站,登录公司一个帐号使用的公司网络,使用网上…...
HMTL+JS+CSS实现贪吃蛇游戏,包含有一般模式,困难模式,还有无敌模式
HMTLJSCSS实现贪吃蛇游戏,包含有一般模式,困难模式,还有无敌模式(可以穿墙死不了,从左边进去可以从右边出来),显示当前分数和最高分,吃到的球颜色可以叠加到蛇身体上 为了适配手机端…...
vue将页面导出成word
方法一:使用 html-docx-js html-docx-js 是一个轻量级的库,可以将 HTML 转换为 Word 文档。 安装依赖 首先安装 html-docx-js: Bash深色版本 npm install html-docx-js --save创建导出逻辑 在 Vue 组件中实现导出功能的代码如下࿱…...
Spring MVC 页面跳转方案与区别
SpringMVC 的页面跳转方案主要分为 转发(Forward) 和 重定向(Redirect) 两类,具体实现方式和区别如下: 一、页面跳转方案 1. 转发(Forward) 默认方式:直…...
Open GL ES ->纹理贴图,顶点坐标和纹理坐标组合到同一个顶点缓冲对象中进行解析
XML文件 <?xml version"1.0" encoding"utf-8"?> <com.example.myapplication.MyGLSurfaceView2 xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/glSurfaceView"android:layout_width"matc…...
题解:AT_arc050_c [ARC050C] LCM 111
一道比较简单的题。(我绝对不会告诉你这题我改了很久) 题目意思很简单,我就不过多解释了,我们直接进入正题。 题目要我们求 a a a 个 1 1 1 组成的数与 b b b 个 1 1 1 组成的数的最小公倍数除以 m m m 后的余数。先不考虑…...
【408--考研复习笔记】计算机网络----知识点速览
目录 一、计算机网络体系结构 1.计算机网络的定义与功能: 2.网络体系结构相关概念: 3.OSI 七层模型与 TCP/IP 模型: 4.通信方式与交换技术: 电路交换 报文交换 分组交换 5.端到端通信和点到点通信: 6.计算机…...
ISIS报文
IS-IS 报文 目录 IS-IS 报文 一、报文类型与功能 二、报文结构解析 三、核心功能特性 四、典型应用场景 五、抓包数据分析 六、总结 IS-IS(中间系统到中间系统)协议报文是用于链路状态路由协议中网络设备间交换路由信息的关键载体,其设…...
FPGA——分秒计数器
文章目录 一、实验任务二、系统模块三、工程源码四、管脚信息五、运行结果参考资料总结 一、实验任务 在DE2-115板子上用 Verilog编程实现一个分秒计数器,并具备按键暂停、按键消抖功能。 二、系统模块 分频模块 高频时钟(如50MHz)分频得到…...
【Java】JVM
一、JVM体系结构 1、虚拟机概述 虚拟机(Virtual Machine):一台虚拟的计算机,指一种特殊的软件,他可以在计算机平台和终端用户之间创建一种环境,而终端用户则是基于这个软件所创建的环境来操作软件。虚拟机…...
vue中使用geoscene无法出现弹窗
项目场景: 平日对地图加载使用不复杂的情况下,我通常采用leaflet去加载地图做一些简单的操作。但是最近需要用到arcgis发布的地图服务加载三维场景,于是又用回了geoscene(arcgis国产化)。这下暴露出很多的问题&#x…...
【Go】数组
数组Array 重点: 数组是值类型 注意点: 1. 数组:是同一种数据类型的固定长度的序列。2. 数组定义:var a [len]int,比如:var a [5]int,数组长度必须是常量,且是类型的组成部分。一旦定义&…...
【运维】Centos硬盘满导致开机时处于加载状态无法开机解决办法
Centos硬盘存储过满导致无法加载 一、准备1.现象2.根因分析3.制定救援方案问题1:无法进入系统确定分析结论 问题2:磁盘数据过多 4.后处理 一、准备 1.现象 Centos虚拟机界面卡顿,随后进行了重启操作,发现重新启动界面一直卡在加…...
JVM——模型分析、回收机制
方法区:存储已被虚拟机加载的类元数据信息(元空间) 堆:存放对象实例,几乎所有的对象实例都在这里分配内存 虚拟机栈:虚拟机栈描述的是|ava方法执行的内存模型:每个方法被执行的时候都会同时创建一个栈帧(Stack Frame)用于存储局…...
kafka 4.x docker启动kafka4.0.0 docker-compose启动最新版kafka 如何使用docker容器启动最新版kafka
1. 镜像选择标签: https://hub.docker.com/r/bitnami/kafka/tags 2. 命令: docker pull bitnami/kafka:4.0.0 3. docker-compose.yml 启动kafka4.0.0: version: 3services:kafka:image: bitnami/kafka:4.0.0container_name: kafkaports:- &…...
BUUCTF-web刷题篇(6)
15.PHP 知识点: ①__wakeup()//将在反序列化之后立即调用(当反序列化时变量个数与实际不符是会绕过)我们可以通过一个cve来绕过:CVE-2016-7124。将Object中表示数量的字段改成比实际字段大的值即可绕过wakeup函数。条件:PHP5<…...
MySQL篇(一):慢查询定位及索引、B树相关知识详解
MySQL篇(一):慢查询定位及索引、B树相关知识详解 MySQL篇(一):慢查询定位及索引、B树相关知识详解一、MySQL中慢查询的定位(一)慢查询日志的开启(二)慢查询日…...
QT之QML(简单示例)
需求一:点击按钮弹出菜单,并且自定义菜单弹出位置。 mouse.x 和 mouse.y 获取的是相对于 MouseArea(在这个例子中是 Button)左上角的局部坐标。如果你想要在鼠标点击位置显示 Menu,你需要将这个局部坐标转换为相对于应…...
自动化释放linux服务器内存脚本
脚本说明 使用Linux的Cron定时任务结合Shell脚本来实现自动化的内存释放。 脚本用到sync系统命令 sync的作用:sync 是一个 Linux 系统命令,用于将文件系统缓存中的数据强制写入磁盘。 在你执行reboot、poweroff、shutdown命令时,系统会默认执…...
Linux中的权限管理
一、权限的概念 在 Linux 系统的架构里,权限是构建安全堡垒的基石,精准界定了不同用户对文件与目录的操作边界,对系统安全的维护以及数据完整性的保障起着决定性作用。 1.权限的三种基础类别: 权限对文件的影响对目录的影响 读(r…...
Java对象与JSON字符串的互转
最近,工作中会涉及到Java对象与JSON字符串相互转换,虽然说并不难,但打算还是梳理一番,主要内容有: JSON 字符串 转 普通对象 普通对象 转 JSON 字符串 JSON 字符串数组 转 List 集合对象 List 集合对象 转 JSON 字符串…...
[笔记.AI]向量化
(借助 DeepSeek-V3 辅助生成) 向量化的定义 向量化(Vectorization) 是将文本、图像、音频等非结构化数据转换为高维数值向量(即一组数字)的过程。这些向量能够捕捉数据的语义、特征或上下文信息&#x…...
NSSCTF(MISC)—[justCTF 2020]pdf
相应的做题地址:https://www.nssctf.cn/problem/920 binwalk分离 解压文件2AE59A.zip mutool 得到一张图片 B5F31内容 B5FFD内容 转换成图片 justCTF{BytesAreNotRealWakeUpSheeple}...
Angular的理解
Angular 是一个由 Google 维护的全功能前端框架,适合构建复杂的企业级应用。它采用 TypeScript 作为首选语言,提供了一套完整的解决方案,包括数据绑定、依赖注入、路由、表单处理等。 1. Angular 的核心概念 1.1 组件化架构 Angular 应用由…...
广告推荐算法:COSMO算法与A9算法的对比
COSMO算法与A9算法的概念解析 1. A9算法 定义与背景: A9算法是亚马逊早期为电商平台研发的核心搜索算法,主要用于优化商品搜索结果的排序和推荐,其核心逻辑围绕产品属性与关键词匹配展开。自2003年推出以来,A9通过分析商品标题…...
10. 七大排序(含四种版本快排及优化) ******
排序算法时间复杂度(平均)时间复杂度(最坏)时间复杂度(最好)空间复杂度稳定性主要使用场景直接插入排序O(n)O(n)O(n)O(1)稳定小规模数据或基本有序数据希尔排序O(n^1.3)O(n)O(n log n)O(1)不稳定中等规模数据,对稳定性无要求选择排序O(n)O(n)O(n)O(1)不稳定小规模数…...
以下是C/C++后台开发常见的高概率面试题
一、语言基础 多态的实现 通过虚函数表(vtable)实现动态绑定,运行时根据对象类型调用对应的函数。虚函数通过virtual关键字声明,子类可重写基类虚函数112。 指针与引用的区别 指针是变量,存储地址,支持多…...
CentOS-查询实时报错日志-查询前1天业务报错gz压缩日志
最新版本更新 https://code.jiangjiesheng.cn/article/364?from=csdn 推荐 《高并发 & 微服务 & 性能调优实战案例100讲 源码下载》 1. 查询实时报错日志 物理路径(带*的放在靠后,或者不用*) cd /home/logs/java-gz-log-dir && tail -2000f java-gz-l…...
破界·共生:生成式人工智能(GAI)认证重构普通人的AI进化图谱
在当今这个科技日新月异的时代,人工智能(AI)正以惊人的速度改变着我们的世界。从智能家居到自动驾驶,从医疗诊断到金融分析,AI的应用已经渗透到社会生活的方方面面。面对如此迅猛的发展态势,我们不禁要问:人工智能的未来将走向何方?普通人又该如何把握这一历史机遇,学…...