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

Android学习总结之MMKV(代替SharedPreferences)

Q1:SharedPreferences 为什么会导致 ANR?MMKV 如何从根本上解决?

高频考察点:Android 主线程阻塞原理、SP 同步 / 异步机制缺陷、MMKV 内存映射技术

  • SP 导致 ANR 的三大元凶
    1. 同步提交(commit ()):直接在主线程执行磁盘 I/O,写入 XML 文件耗时可达 100ms+(尤其首次写入或文件较大时)。
    2. 异步陷阱(apply ()):虽异步写入,但依赖 QueuedWork 机制,在 Activity.onStop()/onDestroy() 时会强制等待所有未完成的 apply 任务,若任务堆积则阻塞主线程。
    3. 首次加载阻塞:首次调用 getXXX() 时,若 SP 尚未加载完数据(异步加载未完成),主线程会陷入 wait() 无限等待,典型场景:启动页读取配置。
  • MMKV 的解决方案
    • 内存映射(mmap):将文件直接映射到内存地址空间,写入时修改内存即可,由操作系统异步刷盘(非阻塞),省去传统 I/O 的用户态 / 内核态数据拷贝(4 次→0 次)。
    • 无队列化异步:取消 QueuedWork 依赖,写入任务提交到独立线程池(默认线程数与 CPU 核心数相关),不阻塞 Activity 生命周期。
    • 预加载机制:初始化时直接映射文件,首次读取无需等待加载,数据立即可见。
  • 面试应答模板
    “SP 的 ANR 根源在于同步 I/O 和生命周期阻塞,而 MMKV 通过 mmap 实现内存直接操作,异步刷盘且不依赖 Activity 队列,从底层消除了主线程阻塞风险。某大厂曾统计,迁移 MMKV 后 ANR 率从 18% 降至 0.3%,核心就是解决了这两个痛点。”
Q2:MMKV 如何实现多进程数据一致性?相比 SP 的 ContentProvider 方案有何优势?

高频考察点:跨进程通信、文件锁机制、Android 多进程坑点

  • SP 多进程的致命缺陷
    • 文件锁(FileLock)非原子操作,跨进程读写时可能出现 “写一半” 的情况(如进程 A 写入时被进程 B 打断,导致 XML 文件损坏),某电商 APP 曾因此出现 37% 的数据错乱率。
    • 依赖 ContentProvider 通知变更时,需手动处理序列化和回调,实现复杂且易漏更新。
  • MMKV 的多进程方案
    1. 原子锁(flock):基于 Linux 系统级 flock() 锁,保证跨进程读写时的原子性(写时加独占锁,读时加共享锁),避免数据竞争。
    2. Ashmem 匿名内存:跨进程传输数据时,通过 Android 提供的 Ashmem(匿名共享内存)传递内存地址,避免敏感数据落地到文件,提升安全性和传输效率。
    3. 变更广播:内置 ContentProvider 实现跨进程变更通知,自动触发其他进程的缓存刷新,无需开发者手动处理。
  • 对比优势
    方案数据一致性性能实现复杂度安全性
    SP+ContentProvider弱(需手动)低(序列化开销)数据落盘传输
    MMKV 原生多进程强(原子锁)高(内存映射)低(一行代码)Ashmem 防泄漏
  • 面试应答模板
    “MMKV 通过系统级 flock 锁保证原子操作,结合 Ashmem 避免数据落盘传输,相比 SP 的文件锁和 ContentProvider 方案,既解决了数据错乱问题,又简化了多进程开发(只需传入 MULTI_PROCESS_MODE 参数)。微信支付场景中,多进程并发读写的成功率从 SP 的 63% 提升至 MMKV 的 99.99%。”
Q3:Protobuf 序列化相比 XML 有哪些技术优势?为什么 MMKV 选择它而非 JSON?

高频考察点:数据格式对比、性能优化原理、二进制协议特性

  • Protobuf 核心优势(对比 XML/JSON)
    1. 体积更小:二进制编码(非文本),去除冗余格式(如标签名),数据体积比 XML 缩小 45%(例:100KB XML → 55KB Protobuf),减少磁盘 I/O 和内存占用。
    2. 解析更快:无需解析复杂标签结构,通过字段编号(如 int32 age = 1 中的 1)直接定位数据,序列化 / 反序列化速度比 XML 快 5 倍(万次操作:Protobuf 20ms vs XML 100ms)。
    3. 类型安全:基于 IDL(接口定义语言)生成代码,编译期检查数据类型,避免运行时解析错误(如 XML 中字符串误读为数字)。
  • 为何不选 JSON?
    • JSON 是文本格式,需频繁进行字符解析(如 {}""),性能低于二进制协议。
    • 动态类型导致反序列化时需额外类型判断,内存占用更高(需解析成 Map/List 结构)。
  • 面试应答模板
    “Protobuf 的二进制编码和字段编号机制,使其在性能和体积上碾压 XML/JSON。MMKV 作为高频读写的存储方案,选择 Protobuf 能大幅减少 I/O 耗时和内存占用,这也是为什么在 1000 次写入测试中,MMKV 仅需 7ms,而 SP 需 1200ms—— 其中 30% 的性能提升来自 Protobuf 编码优化。”
Q4:MMKV 如何避免 mmap 导致的内存泄漏?LRU 缓存机制如何实现?

高频考察点:内存管理、Android 资源回收、WeakReference 应用

  • mmap 潜在风险
    • 若长期保持文件映射,且实例未被释放,可能导致内存占用过高(尤其多实例场景)。
  • MMKV 的解决方案
    1. LRU 缓存淘汰:内部维护一个 LRUCache,根据最近使用时间自动释放长时间未访问的 MMKV 实例的 mmap 内存,默认最大缓存数量为 64(可通过 MMKVConfig 配置)。
    2. 弱引用关联 Context:实例持有对 Context 的 WeakReference,避免因 Context 被 MMKV 强引用导致的 Activity 泄漏(如非静态内部类持有 Activity 引用)。
    3. 进程退出自动释放:mmap 映射的内存由操作系统管理,进程结束后自动回收,无需手动释放。
  • 面试应答模板
    “MMKV 通过 LRU 缓存和弱引用机制双重保障内存安全。LRU 会淘汰最少使用的实例的 mmap 映射,而弱引用避免 Context 泄漏。实际开发中,即使创建大量 MMKV 实例,内存占用也会稳定在合理范围 —— 某金融 APP 实测,同时维护 200 个实例时,内存波动不超过 5MB。”
Q5:迁移 MMKV 时需要注意哪些兼容性问题?如何验证迁移是否成功?

高频考察点:数据迁移实战、异常处理、灰度测试

  • 迁移关键细节
    1. 数据格式转换
      • SP 支持的 Set<String> 类型,MMKV 需通过 encodeStringSet()/decodeStringSet() 处理(底层用 Protobuf 重复字段存储)。
      • 布尔 / 数值类型可无缝迁移,但需注意 SP 的 getXXX(key, default) 中默认值的处理(MMKV 无默认值概念,需显式判断 containsKey())。
    2. 多进程模式适配
      • 若原 SP 依赖 Context.MODE_MULTI_PROCESS(已废弃),需手动切换为 MMKV 的 MULTI_PROCESS_MODE,否则可能出现数据不同步。
    3. 迁移验证步骤
      • 灰度阶段对比新旧存储的读写耗时、内存占用。
      • 编写自动化测试:随机写入 10 万 + 条数据,验证迁移前后 key-value 一致性(推荐用 MD5 校验整体数据)。
      • 监控 ANR 率和 Crash 率,重点关注首次初始化和多进程场景。
  • 面试应答模板
    “迁移时需注意数据格式(如 Set 类型)和多进程模式的适配,建议通过 MMKV.importFromSharedPreferences(oldSp) 一键迁移,同时保留旧 SP 一段时间(如 7 天)用于数据回滚。某社交 APP 迁移时,通过灰度测试发现 getBoolean(key, true) 在 MMKV 中需先判断 containsKey(key),避免了默认值差异导致的逻辑错误。”

MMKV 实战操作代码详解(带核心注释)

1. 基础初始化与全局配置(必考点:初始化原理)
// MMKV 初始化(建议在 Application.onCreate() 中执行)
class MyApplication : Application() {override fun onCreate() {super.onCreate()// 基础初始化(默认存储路径:/data/data/包名/files/mmkv/)MMKV.initialize(this) // 高级配置(面试常问:如何优化内存/性能)val mmkvConfig = MMKVConfig.defaultMMKVConfig(this).apply {// 配置 LRU 缓存大小(默认 64 个实例,防止内存泄漏)maxCacheSize = 100 // 启用文件加密(AES-CFB-128 模式,需提供 16 字节密钥)cryptoKey = "1234567890123456".toByteArray() // 多进程模式(核心:flock 原子锁)multiProcessEnable = true }// 使用自定义配置初始化(适用于复杂场景)MMKV.initialize(mmkvConfig, this) }
}// 获取默认实例(全局唯一,共享同一存储文件)
val defaultKV = MMKV.defaultMMKV() 
// 获取指定 ID 的实例(支持多文件隔离,如不同业务模块独立存储)
val userKV = MMKV.mmkvWithID("user_config") 

注释解析

  • 初始化支持 默认配置 和 自定义配置mmkvWithID 实现多文件隔离,避免不同模块数据混杂(如用户配置 vs 业务缓存)。
  • cryptoKey 配置加密时,必须是 16/24/32 字节长度(对应 AES-128/192/256),面试可追问:“为何选择 CFB 模式?”(支持流式加密,适配 MMKV 的增量写入)。
2. 数据读写实战(全类型覆盖 + 性能优化点)
// 写入基础数据类型(原子操作,无锁设计)
fun writeBaseData() {val kv = MMKV.defaultMMKV()// 字符串(自动处理空值,SP 需手动判空)kv.encode("user_name", "John") // 数值类型(支持 Long/Double 等大字段,SP 性能随字段大小下降明显)kv.encode("user_age", 25) kv.encode("user_weight", 65.5f) // 布尔值(底层 Protobuf 用 varint 编码,比 XML 的 <bool> 标签更紧凑)kv.encode("is_vip", true) // 数组/集合(SP 的 Set<String> 需手动处理,MMKV 原生支持)kv.encode("favorite_fruits", setOf("Apple", "Banana")) 
}// 读取数据(无首次加载阻塞,直接返回内存映射数据)
fun readBaseData(): String {val kv = MMKV.defaultMMKV()// 安全读取(提供默认值,与 SP 行为一致)val name = kv.decodeString("user_name", "Guest") // 集合读取(直接返回 Set,无需手动解析 XML)val fruits = kv.decodeStringSet("favorite_fruits", emptySet()) // 存在性检查(SP 需通过 getAll() 间接判断,MMKV 直接支持)val hasAge = kv.containsKey("user_age") return name
}// 批量读写(核心优化:避免多次 I/O,SP 需多次 commit/apply)
fun batchWrite() {val kv = MMKV.defaultMMKV()// 开启事务(保证批量操作原子性,失败自动回滚)kv.withTransaction {it.encode("key1", "value1")it.encode("key2", "value2")}
}

注释解析

  • withTransaction 实现批量操作原子性,避免部分写入失败(SP 无此机制,需手动保证一致性)。
  • 数值类型写入时,MMKV 通过 Protobuf 的 Varint 编码(小数值用 1-2 字节存储),比 XML 的文本存储(如 25 需 2 字节字符)更高效。
3. 多进程场景实战(面试重灾区:跨进程一致性)
// 主进程初始化多进程实例(关键参数:MULTI_PROCESS_MODE)
val crossProcessKV = MMKV.mmkvWithID("cross_process_config", MMKV.MULTI_PROCESS_MODE // 启用系统级 flock 锁
)// 子进程读取数据(自动感知主进程变更)
fun childProcessRead() {val kv = MMKV.mmkvWithID("cross_process_config", MMKV.MULTI_PROCESS_MODE)// 注册变更监听(跨进程时自动触发,替代 SP 的 ContentObserver)kv.addOnContentChangedListener { key, oldValue, newValue ->Log.d("MMKV", "Key $key changed from $oldValue to $newValue")}// 读取数据(底层通过 Ashmem 共享内存,避免数据落盘传输)val value = kv.decodeString("cross_key")
}// 主进程写入(自动通知子进程)
fun mainProcessWrite() {crossProcessKV.encode("cross_key", "main_process_value")// 强制刷盘(可选,默认异步刷盘,如需立即持久化调用)crossProcessKV.flush() 
}

注释解析

  • MULTI_PROCESS_MODE 底层使用 flock() 锁,保证跨进程读写原子性(SP 的 MODE_MULTI_PROCESS 已废弃,且实现不可靠)。
  • addOnContentChangedListener 内置跨进程广播(基于 ContentProvider),无需手动实现 IPC 通信(SP 需自定义 BroadcastReceiver)。
4. 数据加密实战(金融 / 支付场景必备)
// 初始化加密实例(关键:设置 cryptoKey,面试问:如何保护密钥?)
val encryptedKV = MMKV.mmkvWithID("encrypted_config",MMKV.MULTI_PROCESS_MODE,"16字节长度的密钥".toByteArray() // AES-CFB-128 密钥(必选)
)// 写入加密数据(底层自动加密,不影响上层 API)
encryptedKV.encode("secret_key", "敏感信息如支付密码")// 读取加密数据(自动解密,性能损耗仅 15%,远优于 SP 手动加密)
val secretValue = encryptedKV.decodeString("secret_key")// 密钥保护方案(面试延伸:MMKV 如何防反编译?):
// 1. 密钥存储在 Native 层(通过 JNI 从 SO 库获取,非 Java 层硬编码)
// 2. 使用白盒加密(White-Box Cryptography),防止密钥被逆向工程提取

注释解析

  • 加密对上层 API 透明,写入即加密、读取即解密,无需开发者处理加解密逻辑(SP 需手动实现,易出错)。
  • 密钥建议通过 动态生成 + 安全通道传输(如从服务器下发),避免硬编码在 APK 中。
5. 数据迁移实战(从 SP 无缝切换,面试必问迁移步骤)
Step 1:初始化 MMKV(关键配置)
// 在 Application 中初始化 MMKV(建议与 SP 并行运行一段时间)
class MyApplication : Application() {override fun onCreate() {super.onCreate()// 初始化 MMKV(默认配置,如需加密/多进程在此配置)MMKV.initialize(this) // 保留旧 SP 实例(用于对比验证和回滚)spMigrationHelper = getSharedPreferences("old_sp", Context.MODE_PRIVATE)}companion object {lateinit var spMigrationHelper: SharedPreferences // 全局旧 SP 引用}
}

核心注意

  • 初始化 MMKV 时 不建议立即删除 SP,需先并行运行,确保迁移后功能正常
  • 若旧 SP 使用多进程模式(MODE_MULTI_PROCESS),MMKV 需显式配置 MULTI_PROCESS_MODE
Step 2:一键导入 SP 数据(核心迁移接口)
// 核心迁移方法(一行代码完成基础数据迁移)
fun migrateFromSP() {val oldSP = MyApplication.spMigrationHelper // 旧 SP 实例val mmkv = MMKV.defaultMMKV() // MMKV 目标实例// 1. 调用官方迁移工具(内部处理 XML 解析与 Protobuf 转换)MMKV.importFromSharedPreferences(oldSP) // 2. 特殊类型处理(SP 的 Set<String> 需手动迁移,MMKV 不支持自动转换)oldSP.getStringSet("old_set_key", emptySet())?.let {mmkv.encodeStringSet("new_set_key", it) // 显式调用 StringSet 接口}// 3. 处理默认值差异(SP 的 getXxx(key, default) 在 MMKV 中需先判断 key 是否存在)if (!mmkv.containsKey("old_key_with_default")) {mmkv.encode("old_key_with_default", "default_value") // 手动写入默认值}
}

源码级解析

  • importFromSharedPreferences 内部逻辑:
    1. 读取 SP 的 XML 文件字节流
    2. 解析 XML 节点,提取 key-value 对
    3. 按 MMKV 的 Protobuf 格式重新编码并写入
  • Set<String> 迁移陷阱:SP 的 Set<String> 在 XML 中存储为多个 <string-array> 节点,MMKV 需通过 encodeStringSet() 显式处理,否则会丢失
Step 3:数据一致性验证(生产环境必做)
// 验证迁移后所有数据与旧 SP 完全一致
fun verifyMigrationIntegrity() {val oldSP = MyApplication.spMigrationHelperval mmkv = MMKV.defaultMMKV()// 1. 对比 key 集合(排除 MMKV 自动生成的元数据 key)val mmkvKeys = mmkv.allKeys().toSet()val spKeys = oldSP.all.keys.toSet()check(mmkvKeys.containsAll(spKeys)) { "Key missing: ${spKeys - mmkvKeys}" }// 2. 逐类型对比 value(覆盖所有数据类型)oldSP.all.forEach { (key, spValue) ->when (spValue) {is String -> {val mmkvValue = mmkv.decodeString(key)check(mmkvValue == spValue) { "String mismatch: $key" }}is Int -> {val mmkvValue = mmkv.decodeInt(key, -1) // 提供默认值避免 NPEcheck(mmkvValue == spValue) { "Int mismatch: $key" }}is Boolean -> {val mmkvValue = mmkv.decodeBool(key, false)check(mmkvValue == spValue) { "Boolean mismatch: $key" }}// 处理 Set<String>(SP 独有,需单独验证)is Set<*> -> {@Suppress("UNCHECKED_CAST")val spStringSet = spValue as Set<String>val mmkvStringSet = mmkv.decodeStringSet(key, emptySet())check(mmkvStringSet == spStringSet) { "Set mismatch: $key" }}}}// 3. 性能对比验证(可选,记录迁移前后读写耗时变化)val spReadTime = measureTimeMillis { oldSP.all }val mmkvReadTime = measureTimeMillis { mmkv.allKeys() }Log.d("Migration", "SP Read Time: $spReadTime ms, MMKV Read Time: $mmkvReadTime ms")
}

验证关键点

  • 默认值处理:MMKV 的 decodeXxx(key) 若 key 不存在返回 null(除基础类型可指定默认值),而 SP 的 getXxx(key, default) 自动返回默认值,需在迁移后补全默认值逻辑
  • 大文件迁移:若 SP 文件超过 1MB,importFromSharedPreferences 可能耗时较长,建议在子线程执行,避免 ANR
Step 4:逐步废弃旧 SP(灰度与回滚策略)
// 生产环境推荐的灰度迁移流程
fun migrateInGrayMode() {// 阶段 1:双写阶段(同时写入 SP 和 MMKV,持续 1-2 个版本)fun writeData(key: String, value: String) {// 旧逻辑:写入 SPoldSP.edit().putString(key, value).apply() // 新逻辑:写入 MMKVMMKV.defaultMMKV().encode(key, value) }// 阶段 2:单读 MMKV + 对比验证(确认无误后关闭 SP 写入)fun readData(key: String): String {val mmkvValue = MMKV.defaultMMKV().decodeString(key)val spValue = oldSP.getString(key, "")// 对比双写数据一致性(用于监控报警)if (mmkvValue != spValue) {上报迁移不一致异常(key, mmkvValue, spValue)}return mmkvValue ?: spValue // 过渡期回退到 SP(防止 MMKV 数据缺失)}// 阶段 3:完全废弃 SP(确认 MMKV 稳定后清理旧数据)if (迁移验证通过 && 灰度周期结束) {oldSP.edit().clear().commit() // 清空旧 SP 数据删除旧 SP 文件(context, "old_sp.xml") // 物理删除文件(需适配 Android 10+ 存储策略)}
}// 辅助函数:删除旧 SP 文件(注意文件路径)
fun 删除旧 SP 文件(context: Context, spFileName: String) {val spDir = context.filesDir.resolve("shared_prefs")val spFile = spDir.resolve("$spFileName.xml")spFile.delete()
}

生产级最佳实践

  • 双写阶段:避免因迁移工具不完善导致的数据丢失,确保新老存储数据实时一致
  • 灰度监控:通过 APM 工具(如 Firebase、Bugly)监控 mmkvValue != spValue 的异常比例,设定阈值(如 >0.1% 触发回滚)
  • 文件清理:Android 10+ 需通过 Context.deleteFile() 或直接操作文件路径,避免残留旧 XML 文件占用存储
6. 性能压测实战(面试加分项:如何复现 300 倍性能差)
// 写入性能测试(对比 SP/MMKV)
fun testWritePerformance() {val sp = getSharedPreferences("sp_test", Context.MODE_PRIVATE)val mmkv = MMKV.mmkvWithID("mmkv_test")val iterations = 1000// SP 同步写入val spStart = System.currentTimeMillis()repeat(iterations) {sp.edit().putString("key_$it", "value_$it").commit() // 同步提交,阻塞主线程}val spTime = System.currentTimeMillis() - spStart// MMKV 异步写入(默认异步刷盘,非阻塞)val mmkvStart = System.currentTimeMillis()repeat(iterations) {mmkv.encode("key_$it", "value_$it") // 直接写入内存映射,无需等待磁盘}val mmkvTime = System.currentTimeMillis() - mmkvStartLog.d("Performance", "SP Time: $spTime ms, MMKV Time: $mmkvTime ms")// 典型输出:SP 1200ms vs MMKV 7ms(300 倍差距,取决于设备性能)
}

注释解析

  • SP 的 commit() 同步写入在多次调用时累积阻塞时间,而 MMKV 的 encode() 基于 mmap 直接操作内存,耗时几乎固定(仅内存操作 + 极短的异步刷盘调度)。
  • 压测时建议在子线程执行,避免影响主线程,结果更接近真实场景。

代码级面试扩展

Q:MMKV 写入时为什么不需要显式调用 commit/apply?


MMKV 使用 内存映射(mmap) 技术,encode() 直接修改内存中的映射区域,数据会由操作系统通过 msync() 异步刷盘(默认策略:延迟 500ms 或内存不足时触发)。
对比 SP:commit() 同步刷盘、apply() 异步但依赖队列阻塞,而 MMKV 的异步是真正的非阻塞,写入性能不受磁盘 I/O 影响(见 testWritePerformance 代码)。

Q:多进程模式下,如何保证多个进程同时写入不冲突?


MMKV 在创建实例时通过 MMKV.MULTI_PROCESS_MODE 启用 flock 系统级文件锁

  • 写操作时加 独占锁(LOCK_EX),确保同一时间只有一个进程写入;
  • 读操作时加 共享锁(LOCK_SH),允许多个进程同时读取。
    该机制比 SP 的 FileLock 更可靠(原子性由内核保证),代码示例见 crossProcessKV 初始化。
Q:加密功能是否会影响性能?如何平衡安全与效率?


MMKV 采用 AES-CFB-128 流式加密,加密过程与数据写入同时进行(无需等待完整数据块),性能损耗约 15%(实测 1000 次加密写入耗时约 10ms,非加密 7ms)。
对比手动加密 SP:需先序列化数据、加密、写入,耗时可能增加 50% 以上,且易因加密逻辑错误导致数据损坏(见 encryptedKV 代码示例)。

Q:MMKV.importFromSharedPreferences 内部是如何实现的?会有性能问题吗?

  • 内部实现:
    1. 读取 SP 的 XML 文件(位于 data/data/包名/shared_prefs/ 目录)
    2. 使用 Android 内置的 XmlPullParser 解析 XML 节点,提取 key-value 对
    3. 按 MMKV 的 Protobuf 格式重新编码,通过 mmap 写入内存映射区域
  • 性能影响:
    • 单文件迁移耗时与 SP 文件大小正相关(1MB 文件约 5ms,10MB 约 50ms)
    • 建议在子线程执行迁移(避免阻塞主线程),可通过 runOnBackgroundThread { MMKV.importFromSharedPreferences(oldSP) } 实现
Q:迁移后发现部分数据丢失,可能的原因有哪些?

  1. Set<String> 未手动迁移:MMKV 的 importFromSharedPreferences 不处理 Set<String> 类型,需额外调用 encodeStringSet()
  2. SP 文件损坏:若 SP 的 XML 文件因磁盘错误损坏(如突然断电),迁移工具会跳过损坏节点
  3. 数据类型不匹配:SP 中存储的非标量类型(如通过 putString() 存储 JSON)需手动解析后再写入 MMKV
Q:生产环境如何保证迁移过程的高可用性?

  • 双写双读:在迁移期间同时读写 SP 和 MMKV,通过一致性校验确保数据实时同步
  • 灰度发布:分批次上线迁移功能(如先开放 1% 用户,逐步扩大到 100%)
  • 回滚策略:保留旧 SP 数据一段时间(如 7 天),若出现大面积异常,可通过 MMKV.exportToSharedPreferences()(需自定义工具)回退数据

相关文章:

Android学习总结之MMKV(代替SharedPreferences)

Q1&#xff1a;SharedPreferences 为什么会导致 ANR&#xff1f;MMKV 如何从根本上解决&#xff1f; 高频考察点&#xff1a;Android 主线程阻塞原理、SP 同步 / 异步机制缺陷、MMKV 内存映射技术 SP 导致 ANR 的三大元凶&#xff1a; 同步提交&#xff08;commit ()&#xf…...

SWiRL:数据合成、多步推理与工具使用

SWiRL&#xff1a;数据合成、多步推理与工具使用 在大语言模型&#xff08;LLMs&#xff09;蓬勃发展的今天&#xff0c;其在复杂推理和工具使用任务上却常遇瓶颈。本文提出的Step-Wise Reinforcement Learning&#xff08;SWiRL&#xff09;技术&#xff0c;为解决这些难题带…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】7.2 PostgreSQL与Python数据交互(psycopg2库使用)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL与Python数据交互&#xff1a;psycopg2库实战指南一、引言&#xff1a;数据交互的桥梁1.1 psycopg2核心优势 二、环境准备与基础连接2.1 安装配置2.1.1 安装psyco…...

【Prompt工程—文生图】案例大全

目录 一、人物绘图 二、卡通头像 三、风景图 四、logo设计图 五、动物形象图 六、室内设计图 七、动漫风格 八、二次元图 九、日常场景图 十、古风神化图 十一、游戏场景图 十二、电影大片质感 本文主要介绍了12种不同类型的文生图技巧&#xff0c;通过加入不同的图像…...

NVM完全指南:安装、配置与最佳实践

发布于 2025年5月7日 • 阅读时间&#xff1a;10分钟 &#x1f4a1; TL;DR: 本文详细介绍了如何完整卸载旧版Node.js&#xff0c;安装NVM&#xff0c;配置阿里云镜像源&#xff0c;以及设置node_global与node_cache目录&#xff0c;打造高效Node.js开发环境。 &#x1f4cb; 目…...

成都养老机器人“上岗”,机器人养老未来已至还是前路漫漫?

近日&#xff0c;成都养老机器人“上岗”引发关注&#xff0c;赛博养老这一概念再次成为人们讨论的焦点&#xff0c;究竟赛博养老未来已来&#xff0c;还是仍需漫长等待&#xff0c;引发诸多思考。 成都研发的养老机器人“上岗”确实标志着智慧养老领域的又一进步&#xff0c;…...

数据中心 第十五次CCF-CSP计算机软件能力认证

总结一下图树算法比如krusal 迪杰斯特拉 prim算法喜欢改变距离定义 或者求别的东西 而拓扑排序喜欢大模拟 本题使用kerusal算法求出最后一条边就可以。 ac代码&#xff1a; #include <iostream> #include <vector> #include <algorithm>using namespac…...

【面试 · 一】vue大集合

目录 vue2 基础属性 组件通信 全局状态管理 vueX 路由 路由守卫 vue3 基础属性 组件通信 全局状态管理 Pinia 路由 路由守卫 vue2、vue3生命周期 setup vue2 基础属性 data&#xff1a;用于定义组件的初始数据&#xff0c;必须是一个函数&#xff0c;返回一个对…...

Java 常用的 ORM框架(对象关系映射)

Java 常用的 ORM&#xff08;对象关系映射&#xff09;框架有以下几种&#xff0c;每种都有其特点和使用场景&#xff1a; Hibernate ● 特点&#xff1a; ○ 完整的 ORM 框架&#xff0c;功能强大。 ○ 支持缓存机制&#xff08;一级缓存、二级缓存&#xff09;。 ○ 支持多种…...

自动化创业机器人:现状、挑战与Y Combinator的启示

自动化创业机器人&#xff1a;现状、挑战与Y Combinator的启示 前言 AI驱动的自动化创业机器人&#xff0c;正逐步从科幻走向现实。我们设想的未来是&#xff1a;商业分析、PRD、系统设计、代码实现、测试、运营&#xff0c;全部可以在monorepo中由AI和人类Co-founder协作完成…...

支持向量机

支持向量机&#xff08;Support Vector Machine&#xff0c;SVM&#xff09;是一种有监督的机器学习算法&#xff0c;可用于分类和回归任务&#xff0c;尤其在分类问题上表现出色。下面将从原理、数学模型、核函数、优缺点和应用场景等方面详细介绍。 原理 支持向量机的基本思…...

华为昇腾910B通过vllm部署InternVL3-8B教程

前言 本文主要借鉴&#xff1a;VLLM部署deepseek&#xff0c;结合自身进行整理 下载模型 from modelscope import snapshot_download model_dir snapshot_download(OpenGVLab/InternVL3-8B, local_dir"xxx/OpenGVLab/InternVL2_5-1B")环境配置 auto-dl上选择单卡…...

ZArchiver解压缩工具:高效解压,功能全面

在使用智能手机的过程中&#xff0c;文件管理和压缩文件的处理是许多用户常见的需求。无论是解压下载的文件、管理手机存储中的文件&#xff0c;还是进行日常的文件操作&#xff0c;一款功能强大且操作简便的文件管理工具都能极大地提升用户体验。今天&#xff0c;我们要介绍的…...

ETL介绍

&#xff08;一&#xff09;ETL介绍 “ETL&#xff0c;是英文Extract-Transform-Load的缩写&#xff0c;用来描述将数据从来源端经过抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;、加载&#xff08;Load&#xff09;至目的端的过程。ETL一词较…...

2025.05.07-华为机考第三题300分

📌 点击直达笔试专栏 👉《大厂笔试突围》 💻 春秋招笔试突围在线OJ 👉 笔试突围OJ 03. 城市紧急救援队伍协同规划 问题描述 智慧城市建设中,卢小姐负责设计一套紧急救援队伍协同系统。城市被规划为一个 n n n \times n...

缓存菜品-04.功能测试

一.功能测试 redis中的数据已缓存 查询数据时并没有发sql 修改鸡蛋汤价格为5元。 缓存数据没有了 价格修改成功 停售启售是一样的。修改后清理&#xff0c;再次查询又被缓存到redis中。...

跨境电商生死局:动态IP如何重塑数据生态与运营效率

凌晨三点的深圳跨境电商产业园&#xff0c;某品牌独立站运营总监李明&#xff08;化名&#xff09;正盯着突然中断的广告投放系统。后台日志显示&#xff0c;过去24小时内遭遇了17次IP封禁&#xff0c;直接导致黑五促销期间损失23%的预期流量。这并非个案——2023年跨境电商行业…...

day 14 SHAP可视化

一、原理——合作博弈论 SHAP&#xff08;SHapley Additive exPlanations&#xff09;是一种用于解释机器学习模型预测结果的方法&#xff0c;它基于合作博弈论中的 Shapley 值概念。Shapley 值最初用于解决合作博弈中的利益分配问题。假设有 n 个参与者共同合作完成一项任务并…...

处理PostgreSQL数据库事务死锁过程

查询pg_locks表&#xff0c;获取未得到满足的锁信息&#xff1a; select * from pg_locks where granted is false ; --查询得不到锁的&#xff0c;那就是两个互相等待对方持有的锁查询活动的事务会话进程&#xff0c;和上一步的锁的事务对应起来&#xff1a; select * from …...

大数据、物联网(IoT)、平台架构与设计重构大模型应用

结合大数据、物联网(IoT)、平台架构与设计重构大模型应用,需构建一个数据驱动、实时响应、弹性扩展的智能系统。以下从技术架构、数据流、核心模块设计三个维度展开: 一、整体架构设计 分层架构(基于云-边-端协同): [物联网设备层] → [边缘计算层] → [大数据平台层]…...

开发 Chrome 扩展中的侧边栏图标设置实录(Manifest V3)

在开发自己的 Chrome 扩展 Pocket Bookmarks&#xff08;口袋书签&#xff09; 的过程中&#xff0c;我遇到了一个看似简单却颇具挑战的问题&#xff1a;如何在扩展的侧边栏显示自定义图标&#xff1f; 这篇文章记录一下我踩过的坑&#xff0c;以及最终的解决方案。 这里说的侧…...

Baumer工业相机堡盟工业相机如何通过BGAPI SDK在Linux系统下设置多个USB相机(C++)

Baumer工业相机堡盟工业相机如何通过BGAPI SDK在Linux系统下设置多个USB相机&#xff08;C&#xff09; Baumer工业相机Baumer工业相机BGAPI SDK在Linux系统下设置USB相机的技术背景Linux系统内核 USB 模块内存的修改内存限制的确定使用 GRUB 引导加载程序修改内存限制使用 U-B…...

zst-2001 历年真题 知识产权

知识产权 - 第1题 发表权有时间限制 其他下面3个没有 c 知识产权 - 第2题 bd是财产权 c 知识产权 - 第3题 b 知识产权 - 第4题 d 知识产权 - 第5题 d 知识产权 - 第6题 d 知识产权 - 第7题 d 知识产权 - 第8题 b是国务院发布的 d没有复制权…...

设备与驱动:UART设备

大部分的嵌入式系统都包括一些I/O设备&#xff0c;例如仪器上的数据显示屏、工业设备上的串口通信、数据采集设备上模拟数据采样、用于保存数据的Flash/SD卡以及网络设备上的以太网接口等&#xff0c;都是嵌入式系统中容易找到的I/O设备例子。 本专栏主要是分享RT-Thread是如何…...

Linux 服务器静态 IP 配置初始化指南

✅ 第一步&#xff1a;确认网络管理方式 运行以下命令判断系统使用的网络管理服务&#xff1a; # 检查 NetworkManager 是否活跃 systemctl is-active NetworkManager# 检查 network&#xff08;旧服务&#xff09;是否活跃 systemctl is-active network或者检查配置路径&…...

【ROS2】Nav2源码之行为树定义、创建、加载

1、简述 在 Navigation2 里,机器人的导航是一项复杂的任务,包含路径规划、避障、恢复机制等多个子任务。行为树能把这些子任务组织成清晰的层次结构,让机器人可以依据不同的情况做出合理的决策。例如,当机器人在导航途中碰到障碍物时,行为树可以决定是重新规划路径、尝试…...

Redis持久化存储介质评估:NFS与Ceph的适用性分析

#作者&#xff1a;朱雷 文章目录 一、背景二、Redis持久化的必要性与影响1. 持久化的必要性2. 性能与稳定性问题 三、NFS作为持久化存储介质的问题1. 性能瓶颈2. 数据一致性问题3. 存储服务单点故障4. 高延迟影响持久化效率.5. 吞吐量瓶颈 四、Ceph作为持久化存储介质的问题1.…...

如何统一修改word中所有英文字母的字体格式

1.需求分析 我想让整篇论文中的所有英文字母格式都修改为Time New Roman格式。 2.直观操作流程 点击左上角开始 --> 点击替换 --> 点击更多 --> 点击特殊格式 --> 选择查找内容为任意字母(Y) --> 将光标点到替换内容 --> 点击格式 --> 点击字体 --> …...

服务器上机用到的设备

服务器上机通常需要以下硬件设备&#xff1a; 服务器主机&#xff1a; CPU&#xff1a;选择高性能的多核处理器&#xff0c;如英特尔至强&#xff08;Xeon&#xff09;系列或AMD EPYC系列&#xff0c;以满足高并发和多任务处理需求。 内存&#xff08;RAM&#xff09;&#xf…...

【Java ee 初阶】多线程(8)

Synchronized优化&#xff1a; 一、锁升级 锁升级时一个自适应的过程&#xff0c;自适应的过程如下&#xff1a; 在Java编程中&#xff0c;有一部分的人不一定能正确地使用锁&#xff0c;因此&#xff0c;Java的设计者为了让大家使用锁的门槛更低&#xff0c;就在synchronize…...

数字孪生大屏UI设计

近年来,5G、大数据、云计算等新一代信息技术的蓬勃发展,计算机仿真技术与拟真软件的成熟运用,让数字孪生技术开始蔓延渗透到“互联网”相关的产业中。数字孪生大屏给予了可视化的数据直观窗口,其中展现的动态映射与实时数据让业务流转效率得到了有效提升,管理、运营和决策都能高…...

【Java ee 初阶】多线程(9)上

一、信号量Semaphore 本质上就是一个计数器&#xff0c;描述了一种“可用资源”的个数 申请资源&#xff08;P操作&#xff09;&#xff1a;使得计数器-1 释放资源&#xff08;V操作&#xff09;&#xff1a;使得计数器1 如果计数器为0了&#xff0c;继续申请资源&#xff…...

eclipse开发环境中缺少JavaEE组件如何安装

新版本eclipse去掉server了吗&#xff1f;在最近新版本的eclipse里面&#xff0c;确实找不到server模块了&#xff0c;无法配置tomcat等web服务器插件了。我们需要自己手工安装一下javaEE组件才行。 1 1&#xff1a;找到自己当前eclipse版本号码 2&#xff1a;去这个地址&…...

stm32之ADC

目录 1.简介2.逐次逼近型ADC3.基本结构4.输入通道5.转换模式6.触发控制7.数据对齐8.转换时间7.校准10.ADC外围电路11.api和结构体11.1 结构体11.2 api1. ADC_DeInit2. ADC_Init3. ADC_StructInit4. ADC_Cmd5. ADC_DMACmd6. ADC_ITConfig7. ADC_ResetCalibration8. ADC_GetReset…...

从电话到V信语音:一款App实现全场景社交脱身

作为一名资深社恐人士&#xff0c;我深知那些无法脱身的社交场合有多煎熬。上周参加一个行业聚会&#xff0c;面对滔滔不绝的陌生人&#xff0c;我如坐针毡却又找不到合适的离场理由。这时我突然想起之前朋友推荐的一款神器应用&#xff0c;它让我得以优雅脱身。今天就来分享这…...

conda init before conda activate

先conda init 然后退出命令窗口&#xff0c;再重新打开命令窗口再conda activate...

MySQL数据库高可用(MHA)详细方案与部署教程

一&#xff1a;MHA简介 核心功能 二&#xff1a;MHA工作原理 三&#xff1a;MHA组件 四&#xff1a;MHA 架构与工具 MHA架构 Manager关键工具 Node工具 五&#xff1a;工作原理与流程 1: 故障检测 2: 故障切换&#xff08;Failover&#xff09; 3 : 切换模式 六&a…...

《Python星球日记》 第44天: 线性回归与逻辑回归

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 专栏:《Python星球日记》,限时特价订阅中ing 目录 一、引言:回归方法的重要性二、线性回归原理与损失函数1. 线性回归的数学模型2. 损失函数:衡量…...

Flutter TabBar / TabBarView 详解

目录 一、引言 二、基本用法 代码解析 三、主要属性 3.1 TabBar 3.2 TabBarView 四、进阶定制&#xff1a;突破默认样式 4.1 视觉样式深度定制 4.2 自定义指示器与标签 4.3 动态标签管理 五、工程实践关键技巧 5.1 性能优化方案 5.2 复杂手势处理 5.3 响应式布局…...

001 环境搭建

&#x1f984; 个人主页: 小米里的大麦-CSDN博客 &#x1f38f; 所属专栏: Linux_小米里的大麦的博客-CSDN博客 &#x1f381; GitHub主页: 小米里的大麦的 GitHub ⚙️ 操作环境: Visual Studio 2022 文章目录 Linux 环境搭建全解析&#xff1a;从历史到实践一、Linux 的起源与…...

Spark-core-RDD入门

RDD基本概念 Resilient Distributed Dataset 叫做弹性分布式数据集&#xff0c;是Spark中最基本的数据抽象&#xff0c;是分布式计算的实现载体&#xff0c;代表一个不可变&#xff0c;可分区&#xff0c;里面的元素并行计算的集合。 - Dataset&#xff1a; 一个数据集合&…...

在scala中,转换算子和行动算子有什么区别

在Scala结合Spark编程中&#xff0c;转换算子&#xff08;Transformation&#xff09;和行动算子&#xff08;Action&#xff09;有以下区别&#xff1a; 执行机制 **转换算子**&#xff1a; 具有惰性求值&#xff08;延迟计算&#xff09;特性 。它对RDD&#xff08;弹性分布…...

六级阅读---2024.12 卷一 仔细阅读1

文章 Imagine youre an alien sent to Earth to document the behaviour of the mammals inhabiting the planet. You stumble into a movie theatre thats showing the latest Hollywood horror film. Several dozen humans are gathered together in a dark, undercoated r…...

驱动开发硬核特训 · 专题篇:Vivante GPU 与 DRM 图形显示体系全解析(i.MX8MP 平台实战)

视频教程请关注 B 站&#xff1a;“嵌入式Jerry”。 一、背景导读&#xff1a;GPU 与 DRM 到底谁负责“显示”&#xff1f; 在嵌入式 Linux 图形系统中&#xff0c;“画面怎么显示出来”的问题&#xff0c;表面看似简单&#xff0c;实则涉及多个内核子系统与用户态组件的协同&…...

智慧医疗时代下的医疗设备智能控费系统解决方案

—以科技赋能医疗控费&#xff0c;构建精细化管理新生态 一、行业背景与现存痛点 &#xff08;一&#xff09;政策驱动与行业挑战 随着DRG/DIP支付改革全面落地、医保基金监管趋严&#xff0c;医疗机构面临“提质增效”与“成本管控”的双重压力。国家卫健委数据显示&#x…...

软件设计师2025

笔记链接 第5章&#xff1a;软件工程基础知识 第6章&#xff1a;结构化开发方法 第10章&#xff1a;网络和信息安全基础知识...

Umi+React+Xrender+Hsf项目开发总结

一、菜单路由配置 1.umirc.ts 中的路由配置 .umirc.ts 文件是 UmiJS 框架中的一个配置文件&#xff0c;用于配置应用的全局设置&#xff0c;包括但不限于路由、插件、样式等。 import { defineConfig } from umi; import config from ./def/config;export default defineCon…...

【软件设计师:数据结构】1.数据结构基础(一)

一 线性表 1.线性表定义 线性表是n个元素的有限序列,通常记为(a1,a2,…,an)。 特点: 存在惟一的表头和表尾。除了表头外,表中的每一个元素均只有惟一的直接前驱。除了表尾外,表中的每一个元素均只有惟一的直接后继。2.线性表的存储结构 (1)顺序存储 是用一组地址连续…...

linux_进程地址空间(虚拟地址空间)

一、进程地址空间是什么&#xff1f; 先看这样一个具体的例子 #include<stdlib.h> #include <stdio.h> #include<unistd.h> int main() {int a1;pid_t idfork();while(1){if(id0){printf("i am child,pid:%d,ppid:%d,a:%d ,&a:%p\n",getpid(…...

计操第四章存储管理

地址再定位...