存储优化(protobuf与mmkv)

存储优化(protobuf与mmkv)

在Android应用开发中,数据存储是一个基础且关键的环节。随着应用功能的日益复杂,数据量的增加,传统的存储方式如SharedPreferences、SQLite等在性能上的局限性逐渐显现。本文将深入探讨两种高效的存储优化方案:Protocol Buffers (protobuf) 和 MMKV,帮助开发者构建更高效、更可靠的数据存储系统。

一、传统存储方式的局限性

在讨论优化方案前,我们先来分析一下传统存储方式存在的问题:

1.1 SharedPreferences的局限

// SharedPreferences的典型使用方式
SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString("username", "张三");
editor.putInt("age", 25);
editor.apply(); // 或commit()

SharedPreferences虽然使用简单,但存在以下问题:

  • 性能问题:apply()方法虽然是异步的,但在主线程中仍可能造成ANR
  • 全量写入:即使只修改一个小的键值,也会导致整个文件的重写
  • 数据类型有限:只支持基本数据类型和String
  • 多进程不安全:在多进程环境下容易出现数据丢失或不一致

1.2 SQLite的局限

// SQLite插入数据示例
ContentValues values = new ContentValues();
values.put("name", "张三");
values.put("age", 25);
db.insert("user", null, values);

SQLite虽然功能强大,但也有其不足:

  • 启动耗时:数据库连接和初始化需要时间
  • 操作复杂:相比键值存储,需要编写更多代码
  • 资源占用:对于简单数据存储来说过于重量级

二、Protocol Buffers (protobuf) 详解

2.1 什么是protobuf

Protocol Buffers是Google开发的一种轻量级、高效的结构化数据序列化机制,具有以下特点:

  • 高效序列化:比XML小3-10倍,比JSON小2-5倍
  • 解析速度快:比XML快20-100倍
  • 语言中立:支持多种编程语言
  • 向前兼容:可以在不破坏现有应用的情况下更新数据结构

2.2 在Android中使用protobuf

2.2.1 添加依赖
dependencies {// protobuf依赖implementation 'com.google.protobuf:protobuf-javalite:3.18.0'// protobuf编译插件implementation 'com.google.protobuf:protoc:3.18.0'
}
2.2.2 定义.proto文件

src/main/proto目录下创建user.proto文件:

syntax = "proto3";package com.example.myapp;option java_package = "com.example.myapp.proto";
option java_multiple_files = true;message User {string name = 1;int32 age = 2;string email = 3;enum Gender {UNKNOWN = 0;MALE = 1;FEMALE = 2;}Gender gender = 4;repeated string hobbies = 5;
}
2.2.3 配置Gradle插件
plugins {id 'com.google.protobuf' version '0.8.18'
}protobuf {protoc {artifact = 'com.google.protobuf:protoc:3.18.0'}generateProtoTasks {all().each { task ->task.builtins {java {option 'lite'}}}}
}
2.2.4 使用生成的类
// 创建User对象
User.Builder userBuilder = User.newBuilder();
User user = userBuilder.setName("张三").setAge(25).setEmail("zhangsan@example.com").setGender(User.Gender.MALE).addHobbies("读书").addHobbies("旅行").build();// 序列化
byte[] userBytes = user.toByteArray();// 将序列化数据保存到文件
FileOutputStream fos = new FileOutputStream("user.pb");
fos.write(userBytes);
fos.close();// 从文件读取并反序列化
FileInputStream fis = new FileInputStream("user.pb");
byte[] data = new byte[fis.available()];
fis.read(data);
fis.close();
User parsedUser = User.parseFrom(data);

2.3 protobuf的优化原理

  1. 紧凑的二进制格式:使用变长编码,小数字占用更少的字节
  2. 字段编号:使用数字而非字符串标识字段,减少存储空间
  3. 可选字段:未设置的字段不占用空间
  4. 高效的解析算法:无需遍历整个数据结构

三、MMKV详解

3.1 什么是MMKV

MMKV是腾讯开源的一个基于mmap的高性能通用key-value组件,专为移动应用设计,用于替代SharedPreferences。

主要特点:

  • 高性能:基于内存映射(mmap),读写性能远超SharedPreferences
  • 多进程安全:支持多进程并发读写
  • 崩溃恢复:进程崩溃不会丢失数据
  • 加密支持:可以对数据进行AES加密

3.2 在Android中使用MMKV

3.2.1 添加依赖
dependencies {implementation 'com.tencent:mmkv:1.2.14'
}
3.2.2 初始化MMKV
// 在Application的onCreate方法中初始化
public class MyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();String rootDir = MMKV.initialize(this);Log.i("MMKV", "mmkv root: " + rootDir);}
}
3.2.3 基本使用
// 获取默认实例
MMKV kv = MMKV.defaultMMKV();// 写入数据
kv.encode("bool", true);
kv.encode("int", 123);
kv.encode("long", 123456789L);
kv.encode("float", 3.14f);
kv.encode("double", 3.14159);
kv.encode("string", "Hello MMKV");
kv.encode("bytes", new byte[]{97, 98, 99});// 读取数据
boolean bValue = kv.decodeBool("bool");
int iValue = kv.decodeInt("int");
long lValue = kv.decodeLong("long");
float fValue = kv.decodeFloat("float");
double dValue = kv.decodeDouble("double");
String sValue = kv.decodeString("string");
byte[] bytes = kv.decodeBytes("bytes");
3.2.4 多进程支持
// 创建多进程实例
MMKV mmkv = MMKV.mmkvWithID("MultiProcess", MMKV.MULTI_PROCESS_MODE);
mmkv.encode("process_id", android.os.Process.myPid());
3.2.5 加密支持
// 创建加密实例
String cryptKey = "my_crypt_key";
MMKV kv = MMKV.mmkvWithID("encrypted", cryptKey);
kv.encode("username", "admin");
kv.encode("password", "123456");

3.3 MMKV的底层实现原理

3.3.1 内存映射(mmap)技术

MMKV的核心技术是内存映射(Memory Mapped Files),它是一种将文件内容映射到进程的虚拟内存空间的技术。

// MMKV中mmap的核心实现(C++代码简化版)
void* mmapFile(int fd, size_t size) {void* ptr = mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);if (ptr == MAP_FAILED) {// 处理映射失败return nullptr;}return ptr;
}

工作原理

  1. 零拷贝:传统文件操作需要先将文件数据从磁盘拷贝到内核空间,再从内核空间拷贝到用户空间。而mmap直接在虚拟内存中操作,避免了这两次拷贝过程。

  2. 页缓存共享:多个进程可以共享同一个文件的页缓存,节省内存。

  3. 延迟加载:操作系统会根据需要将文件数据加载到物理内存,而不是一次性全部加载。

  4. 写回机制:对映射内存的修改不会立即写回磁盘,而是由操作系统的页面置换算法决定何时写回,提高了写入效率。

3.3.2 文件结构与数据组织

MMKV的文件由两部分组成:

  1. 数据文件:存储实际的键值对数据
  2. 元数据文件:存储索引信息,用于快速定位键值对
+----------------+     +----------------+
|  数据文件       |     |  元数据文件     |
|  (mmkv.dat)    |     |  (mmkv.crc)    |
+----------------+     +----------------+
| 键值对1         |     | 键1的位置和长度  |
| 键值对2         |     | 键2的位置和长度  |
| ...            |     | ...            |
+----------------+     +----------------+
3.3.3 写时复制与增量更新

MMKV采用了写时复制(Copy-On-Write)和增量更新策略:

  1. 写时复制:当需要修改数据时,MMKV不会直接修改原有数据,而是创建一个新的副本进行修改。这样可以保证在修改过程中,其他读取操作仍然可以访问旧数据,提高了并发性能。

  2. 增量更新:MMKV不会像SharedPreferences那样每次修改都重写整个文件,而是采用追加写入的方式。新的键值对会被追加到文件末尾,同时更新元数据中的索引信息。

// 伪代码:MMKV的写入流程
void encode(String key, Object value) {// 1. 序列化值byte[] data = serialize(value);// 2. 追加到文件末尾int position = appendToFile(data);// 3. 更新内存中的索引表updateIndex(key, position, data.length);// 4. 异步更新元数据文件asyncUpdateMetaInfo();
}
3.3.4 异步落盘机制

MMKV的写入操作分为两个阶段:

  1. 内存操作:首先在内存中完成数据的修改和索引更新,这一步速度非常快。

  2. 异步落盘:然后通过后台线程将修改异步写入磁盘,不会阻塞主线程。

// 伪代码:MMKV的异步落盘机制
private void asyncSync() {if (!needSync) return;executor.execute(() -> {synchronized (mmapLock) {if (msync(memoryPtr, fileSize, MS_ASYNC) == 0) {needSync = false;}}});
}
3.3.5 数据校验与崩溃恢复

MMKV使用CRC32进行数据校验,确保数据的完整性:

  1. 写入校验:每次写入数据时,会计算数据的CRC校验值并存储。

  2. 读取校验:读取数据时,会重新计算CRC值并与存储的值比较,如果不一致,说明数据已损坏。

  3. 崩溃恢复:当检测到数据损坏时,MMKV会尝试从上一个有效的状态恢复数据。

// 伪代码:MMKV的崩溃恢复机制
private void loadFromFile() {// 读取文件内容byte[] content = readFromMappedFile();// 计算CRC校验值int crc = calculateCRC(content);// 比较校验值if (crc != storedCRC) {// 数据损坏,尝试恢复recoverFromBackup();} else {// 数据正常,解析内容parseContent(content);}
}
3.3.6 多进程并发控制

MMKV通过文件锁和内存屏障等机制实现多进程安全:

  1. 文件锁:在多进程模式下,MMKV使用文件锁来同步对同一文件的访问。

  2. 内存屏障:确保内存操作的可见性和顺序性,防止指令重排导致的数据不一致。

// 伪代码:MMKV的多进程锁实现
private void lockForWrite() {if (isMultiProcess) {// 获取文件锁fileLock.lock();}// 执行写操作// ...if (isMultiProcess) {// 释放文件锁fileLock.unlock();}
}

四、性能对比与选型建议

4.1 性能对比

以下是在中端Android设备上的性能测试结果(数据仅供参考):

操作SharedPreferencesSQLiteMMKV
写入100条数据(ms)32015012
读取100条数据(ms)28485
文件大小(KB)152010

4.2 选型建议

  • 简单键值存储:MMKV是最佳选择,特别是需要高频读写或多进程访问时
  • 大量关系型数据:SQLite仍然是首选
  • 配置信息:小型应用可以继续使用SharedPreferences,大型应用建议迁移到MMKV

五、实战案例:聊天应用的消息缓存优化

5.1 需求分析

聊天应用需要缓存大量消息,要求:

  • 快速读写
  • 支持复杂的消息结构
  • 节省存储空间
  • 崩溃恢复

5.2 实现方案

结合protobuf和MMKV的优势,我们设计如下方案:

  1. 使用protobuf定义消息结构
  2. 使用MMKV存储序列化后的消息
5.2.1 定义消息结构
syntax = "proto3";package com.example.chat;option java_package = "com.example.chat.proto";
option java_multiple_files = true;message ChatMessage {string message_id = 1;string sender_id = 2;string receiver_id = 3;string content = 4;int64 timestamp = 5;enum MessageType {TEXT = 0;IMAGE = 1;VIDEO = 2;AUDIO = 3;}MessageType type = 6;message MediaInfo {string url = 1;int32 duration = 2; // 音视频时长string thumbnail = 3; // 缩略图URL}MediaInfo media_info = 7;bool is_read = 8;
}message Conversation {string conversation_id = 1;repeated ChatMessage messages = 2;int64 last_update_time = 3;
}
5.2.2 消息缓存管理器
public class MessageCacheManager {private static final String TAG = "MessageCacheManager";private static MessageCacheManager instance;private final MMKV mmkv;private MessageCacheManager() {mmkv = MMKV.mmkvWithID("chat_messages");}public static synchronized MessageCacheManager getInstance() {if (instance == null) {instance = new MessageCacheManager();}return instance;}// 保存会话public void saveConversation(Conversation conversation) {try {String key = "conv_" + conversation.getConversationId();byte[] data = conversation.toByteArray();mmkv.encode(key, data);} catch (Exception e) {Log.e(TAG, "保存会话失败", e);}}// 获取会话public Conversation getConversation(String conversationId) {try {String key = "conv_" + conversationId;byte[] data = mmkv.decodeBytes(key);if (data != null) {return Conversation.parseFrom(data);}} catch (Exception e) {Log.e(TAG, "获取会话失败", e);}return null;}// 保存单条消息public void saveMessage(String conversationId, ChatMessage message) {try {Conversation conversation = getConversation(conversationId);Conversation.Builder builder;if (conversation == null) {builder = Conversation.newBuilder().setConversationId(conversationId).setLastUpdateTime(System.currentTimeMillis());} else {builder = Conversation.newBuilder(conversation);// 检查消息是否已存在boolean exists = false;for (ChatMessage existingMsg : conversation.getMessagesList()) {if (existingMsg.getMessageId().equals(message.getMessageId())) {exists = true;break;}}if (exists) {return; // 消息已存在,不重复添加}}// 添加新消息并更新时间戳builder.addMessages(message).setLastUpdateTime(System.currentTimeMillis());// 保存更新后的会话saveConversation(builder.build());} catch (Exception e) {Log.e(TAG, "保存消息失败", e);}}// 删除会话public void deleteConversation(String conversationId) {String key = "conv_" + conversationId;mmkv.removeValueForKey(key);}// 获取所有会话IDpublic List<String> getAllConversationIds() {List<String> result = new ArrayList<>();String[] keys = mmkv.allKeys();if (keys != null) {for (String key : keys) {if (key.startsWith("conv_")) {result.add(key.substring(5)); // 去掉"conv_"前缀}}}return result;}// 清除所有缓存public void clearAll() {mmkv.clearAll();}
}
5.2.3 性能测试与对比

我们对比了传统SQLite方案与Protobuf+MMKV方案在聊天应用中的性能表现:

操作SQLite方案Protobuf+MMKV方案性能提升
写入1000条消息(ms)850120约7倍
读取1000条消息(ms)32080约4倍
存储空间占用(MB)1.80.9约50%
应用启动加载时间(ms)28045约6倍
5.2.4 实现要点与优化技巧
  1. 批量操作优化:对于批量消息的读写,可以一次性操作而不是逐条处理
// 批量保存消息示例
public void saveMessages(String conversationId, List<ChatMessage> messages) {Conversation conversation = getConversation(conversationId);Conversation.Builder builder;if (conversation == null) {builder = Conversation.newBuilder().setConversationId(conversationId);} else {builder = Conversation.newBuilder(conversation);}// 添加所有新消息for (ChatMessage message : messages) {builder.addMessages(message);}builder.setLastUpdateTime(System.currentTimeMillis());saveConversation(builder.build());
}
  1. 消息分页存储:当会话消息过多时,可以按时间段分页存储
// 分页存储示例
private static final int PAGE_SIZE = 100; // 每页100条消息public void saveMessageWithPaging(String conversationId, ChatMessage message) {// 获取当前页的消息String pageKey = getPageKey(conversationId, message.getTimestamp());byte[] pageData = mmkv.decodeBytes(pageKey);ChatMessagePage.Builder pageBuilder;if (pageData == null) {pageBuilder = ChatMessagePage.newBuilder();} else {try {ChatMessagePage page = ChatMessagePage.parseFrom(pageData);pageBuilder = ChatMessagePage.newBuilder(page);// 检查页是否已满if (page.getMessagesCount() >= PAGE_SIZE) {// 创建新页pageKey = createNewPageKey(conversationId);pageBuilder = ChatMessagePage.newBuilder();}} catch (Exception e) {Log.e(TAG, "解析消息页失败", e);pageBuilder = ChatMessagePage.newBuilder();}}// 添加消息到页pageBuilder.addMessages(message);mmkv.encode(pageKey, pageBuilder.build().toByteArray());// 更新会话索引updateConversationIndex(conversationId, pageKey);
}// 获取页面键
private String getPageKey(String conversationId, long timestamp) {// 根据时间戳计算页面IDlong pageId = timestamp / (PAGE_SIZE * 1000); // 每PAGE_SIZE条消息或每1000毫秒一页return "conv_" + conversationId + "_page_" + pageId;
}// 创建新页面键
private String createNewPageKey(String conversationId) {long currentTime = System.currentTimeMillis();return getPageKey(conversationId, currentTime);
}// 更新会话索引
private void updateConversationIndex(String conversationId, String pageKey) {// 在会话索引中记录页面信息// 实际应用中可能需要更复杂的索引结构
}
  1. 加密存储敏感消息:对于敏感内容,可以使用MMKV的加密功能
// 加密存储示例
public void saveEncryptedMessage(String conversationId, ChatMessage message) {// 使用加密实例MMKV encryptedMMKV = MMKV.mmkvWithID("encrypted_" + conversationId, cryptKey);// 保存加密消息String key = "msg_" + message.getMessageId();encryptedMMKV.encode(key, message.toByteArray());
}

六、存储优化相关面试题解析

6.1 基础概念题

Q1: SharedPreferences、SQLite、MMKV各有什么优缺点?适用于哪些场景?

SharedPreferences

  • 优点:使用简单,API友好,适合存储少量简单数据
  • 缺点:全量写入,多进程不安全,可能导致ANR
  • 适用场景:存储应用配置、用户偏好等小型数据

SQLite

  • 优点:支持复杂查询,事务管理,数据完整性强
  • 缺点:启动耗时,API较复杂,资源占用较大
  • 适用场景:结构化数据存储,需要关系查询的场景

MMKV

  • 优点:高性能,多进程安全,崩溃恢复,支持加密
  • 缺点:不支持复杂查询,需要额外依赖
  • 适用场景:高频读写的键值对存储,替代SharedPreferences
Q2: 什么是mmap?它在MMKV中的作用是什么?

:mmap(内存映射)是一种将文件内容映射到进程虚拟内存空间的技术。在MMKV中,mmap的作用有:

  1. 实现零拷贝:避免了传统文件操作中内核空间和用户空间的数据拷贝
  2. 提高读写性能:直接在内存中操作,减少IO开销
  3. 支持多进程共享:多个进程可以共享同一块内存区域
  4. 实现持久化:对映射内存的修改最终会同步到磁盘文件

6.2 实战应用题

Q3: 如何优化SharedPreferences导致的ANR问题?

  1. 使用apply()替代commit():apply()是异步的,不会阻塞主线程
  2. 批量操作:多次修改合并为一次提交
  3. 避免在主线程初始化:将SharedPreferences的初始化放在后台线程
  4. 迁移到MMKV:替换为性能更好的MMKV
  5. 使用ContentProvider预加载:在应用启动时预加载SharedPreferences
// 批量操作示例
SharedPreferences.Editor editor = sp.edit();
// 多次修改
editor.putString("key1", "value1");
editor.putString("key2", "value2");
editor.putString("key3", "value3");
// 一次提交
editor.apply();
Q4: 在大型应用中,如何设计一个高效的数据存储方案?

  1. 分层存储

    • 内存层:使用LruCache缓存热点数据
    • 持久层:根据数据特点选择合适的存储方式
  2. 存储策略

    • 配置信息:MMKV
    • 结构化数据:Room/SQLite
    • 大文件:文件系统
    • 网络数据:结合缓存策略的网络库
  3. 性能优化

    • 异步操作:IO操作放在后台线程
    • 批量处理:合并多次操作
    • 预加载:启动时预加载关键数据
    • 懒加载:按需加载非关键数据
  4. 数据同步

    • 版本控制:使用版本号管理数据更新
    • 增量同步:只同步变更数据
    • 冲突解决:设计冲突检测和解决策略

6.3 原理深度题

Q5: Protobuf相比JSON有哪些优势?其序列化原理是什么?

优势

  1. 更小的体积:二进制格式,比JSON小2-5倍
  2. 更快的解析:解析速度比JSON快5-10倍
  3. 更严格的类型:强类型定义,减少运行时错误
  4. 向前兼容:可以在不破坏现有应用的情况下更新数据结构

序列化原理

  1. 变长编码:使用Varint编码,小数字占用更少字节
  2. 标签-值对:每个字段使用数字标签而非字符串
  3. 紧凑布局:省略默认值和空字段,只序列化有值的字段
  4. 二进制格式:直接使用二进制表示,无需文本转换
// Protobuf编码示例(伪代码)
field1 -> tag(1) + wiretype(0) + varint(123)  // 可能只占2-3个字节
field2 -> tag(2) + wiretype(2) + length(5) + "hello"  // 字符串前有长度前缀
Q6: MMKV如何保证多进程安全和崩溃恢复?

多进程安全

  1. 文件锁:使用文件锁(FileLock)同步多进程访问
  2. 内存屏障:确保内存操作的可见性和顺序性
  3. 原子操作:关键操作保证原子性
  4. 写时复制:修改数据时创建副本,避免读写冲突

崩溃恢复

  1. 数据校验:使用CRC32校验数据完整性
  2. 日志机制:记录操作日志,用于恢复
  3. 备份策略:定期创建数据快照
  4. 增量更新:采用追加写入方式,保留历史数据
  5. 异步落盘:内存操作完成后异步写入磁盘
// MMKV崩溃恢复伪代码
private void recover() {// 1. 检查CRC校验if (!checkDataIntegrity()) {// 2. 尝试从日志恢复if (hasValidLog()) {recoverFromLog();} else {// 3. 尝试从备份恢复recoverFromBackup();}}// 4. 重建索引rebuildIndex();
}

七、总结

本文详细介绍了Android中的高效存储方案:Protocol Buffers和MMKV。通过对比传统存储方式的局限性,我们可以看到这两种方案在性能、稳定性和易用性上的优势。

Protobuf提供了高效的序列化机制,特别适合结构化数据的存储和传输;而MMKV则通过内存映射、异步落盘等技术,为键值对存储提供了极致的性能体验。

在实际应用中,我们可以根据数据特点和应用场景,选择合适的存储方案,甚至可以像案例中展示的那样,结合两者的优势,构建更高效、更可靠的数据存储系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/31971.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【一起学Rust | Tauri2.0框架】基于 Rust 与 Tauri 2.0 框架实现软件开机自启

文章目录 前言 一、准备工作1.1 环境搭建1.2 创建 Tauri 项目1.3 添加依赖 二、实现开机自启的基本原理2.1 开机自启的基本概念2.2 Tauri 应用的生命周期 三、Windows 平台实现3.1 Windows 注册表机制3.2 实现步骤3.3 注意事项 四、Linux 平台实现4.1 Linux systemd 服务4.2 实…

Windows10下docker desktop命令行操作指南(大部分也适用于Linux)

Windows系统最大的特点就是可视化操作&#xff0c;点点鼠标就能操作软件。但是在特殊的情况下&#xff0c;比如docker desktop图标点了之后没反应&#xff0c;但是看后台程序&#xff0c;它又已经运行了&#xff0c;这时候就要使用命令行来操作了。 针对这次情况&#xff0c;所…

静态时序分析:无法满足的生成时钟(TIM-255警告、UITE-461或PTE-075错误)

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 在阅读本文前&#xff0c;强烈建议首先阅读介绍生成时钟的文章&#xff0c;尤其是其中关于时钟极性和反相的相关内容。 静态时序分析&#xff1a;SDC约束命令cr…

计算机网络--访问一个网页的全过程

文章目录 访问一个网页的全过程应用层在浏览器输入URL网址http://www.aspxfans.com:8080/news/index.aspboardID5&ID24618&page1#r_70732423通过DNS获取IP地址生成HTTP请求报文应用层最后 传输层传输层处理应用层报文建立TCP连接传输层最后 网络层网络层对TCP报文进行处…

从零开发Chrome广告拦截插件:开发、打包到发布全攻略

从零开发Chrome广告拦截插件&#xff1a;开发、打包到发布全攻略 想打造一个属于自己的Chrome插件&#xff0c;既能拦截烦人的广告&#xff0c;又能优雅地发布到Chrome Web Store&#xff1f;别担心&#xff0c;这篇教程将带你从零开始&#xff0c;动手开发一个功能强大且美观…

AI智能眼镜主控芯片:技术演进与产业生态的深度解析

一、AI智能眼镜的技术挑战与主控芯片核心诉求 AI智能眼镜作为XR&#xff08;扩展现实&#xff09;技术的代表产品&#xff0c;其核心矛盾在于性能、功耗与体积的三角平衡。主控芯片作为设备的“大脑”&#xff0c;需在有限空间内实现复杂计算、多模态交互与全天候续航&#xf…

elasticsearch 8.17.3部署文档

elasticsearch 8.17.3部署文档 一、架构拓扑 ip主机名角色192.168.241.151slave1master192.168.241.152slave2node1192.168.241.153slave3node2 二、安装包下载——分别下载上传至所有的节点 下载地址https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-…

PySide(PyQT),QGraphicsItem的pos()和scenePos()区别

在QGraphicsItem中&#xff0c;pos()和scenePos()是两个重要的方法&#xff0c;用于描述图形项的位置&#xff0c;但它们的含义和用途有所不同。理解它们的区别对于正确操作和管理QGraphicsItem的位置至关重要。 1. pos()方法 • 定义&#xff1a;pos()返回的是QGraphicsItem在…

Linux 进程控制:创建、终止、等待与程序替换全解析

亲爱的读者朋友们&#x1f603;&#xff0c;此文开启知识盛宴与思想碰撞&#x1f389;。 快来参与讨论&#x1f4ac;&#xff0c;点赞&#x1f44d;、收藏⭐、分享&#x1f4e4;&#xff0c;共创活力社区。 目录 1.进程创建 1-1 fork函数初识​ 1-2 fork函数返回值​ 1-3…

GStreamer —— 2.18、Windows下Qt加载GStreamer库后运行 - “播放教程 6:音频可视化“(附:完整源码)

运行效果 介绍 GStreamer 带有一组将音频转换为视频的元素。他们 可用于科学可视化或为您的音乐增添趣味 player 的本教程展示了&#xff1a; • 如何启用音频可视化 • 如何选择可视化元素 启用音频可视化实际上非常简单。设置相应的标志&#xff0c;当纯音频流为 found&#…

Excel多级联动下拉菜单设置

1.问题描述 现有数据表如下图所示&#xff1a; 该表中包括省、市、县三级目录。 现要将其整理成数据表模板&#xff0c;如下图所示&#xff1a; 要求制作成下拉菜单的形式&#xff0c;且每一级目录的下拉菜单列表要根据上一级目录的内容来确定。 如上图所示&#xff0c;只有…

Web基础:HTML快速入门

HTML基础语法 HTML&#xff08;超文本标记语言&#xff09; 是用于创建网页内容的 标记语言&#xff0c;通过定义页面的 结构和内容 来告诉浏览器如何呈现网页。 超文本&#xff08;Hypertext&#xff09; 是一种通过 链接&#xff08;Hyperlinks&#xff09; 将不同文本、图像…

VSTO(C#)Excel开发2:Excel对象模型和基本操作

初级代码游戏的专栏介绍与文章目录-CSDN博客 我的github&#xff1a;codetoys&#xff0c;所有代码都将会位于ctfc库中。已经放入库中我会指出在库中的位置。 这些代码大部分以Linux为目标但部分代码是纯C的&#xff0c;可以在任何平台上使用。 源码指引&#xff1a;github源…

PostgreSQL学习笔记:PostgreSQL vs MySQL

PostgreSQL 和 MySQL 都是广泛使用的关系型数据库管理系统&#xff0c;它们有以下一些对比&#xff1a; 一、功能特性 1. 数据类型支持 PostgreSQL&#xff1a;支持丰富的数据类型&#xff0c;包括数组、JSON、JSONB、范围类型、几何类型等。对于复杂数据结构的存储和处理非…

Matlab:矩阵运算篇——矩阵

目录 1.定义 实例——创建矩阵 实例——创建复数矩阵 2.矩阵的生成 实例——M文件矩阵 2.利用文本创建 实例——创建生活用品矩阵 3.创建特殊矩阵 实例——生成特殊矩阵 4.矩阵元素的运算 1.矩阵元素的修改 实例——新矩阵的生成 2.矩阵的变维 实例——矩阵维度修…

从0到1入门Docker

一、快速入门 Docker run命令中的常见参数 -d&#xff1a;让容器后台运行--name&#xff1a;给容器命名&#xff08;唯一&#xff09;-e&#xff1a;环境变量-p&#xff1a;宿主机端口映射到容器内端口镜像名称结构&#xff1a;Repository &#xff1a;TAG&#xff08;镜像名&…

Python的浮点数

在 Python 中&#xff0c;有四种内置数值类型&#xff0c;分别是整型整数类型&#xff08;int&#xff09;、布尔类型&#xff08;bool&#xff09;、浮点数类型&#xff08;float&#xff09;和复数类型&#xff08;complex&#xff09;。浮点数默认是双精度类型&#xff0c;占…

从零开始 | C语言基础刷题DAY1

❤个人主页&#xff1a;折枝寄北的博客 DAY1[2025.3.11] 1. 求两个数的较大值2.从键盘输入的两个数的大小关系3.一个整数的奇偶性&#xff0c;请判断4. 考试分数是否通过5.考试成绩是否完美&#xff0c;请判断 1. 求两个数的较大值 题目&#xff1a; 写一个函数求两个整数的较…

[pytest] 配置

这里写目录标题 PytestInitRun3. 根据命令行选项将不同的值传递给测试函数 Report1. 向测试报告标题添加信息2. 分析测试持续时间 pytest --durations33. 增量测试 - 测试步骤--junitxml{report}.xml1. testsuite1.1 在测试套件级别添加属性节点 record_testsuite_property 2. …

物联网商业模式

物联网商业模式是一种战略规划&#xff0c;它融合了物联网技术来创造价值并获取收入。它与传统商业模式的不同之处在于&#xff0c;它利用互联设备来改善运营、提升客户体验以及优化服务项目。在当今由科技驱动的世界中&#xff0c;这种商业模式通过利用实时数据来提供创新服务…