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

如何做一个免费的网站百度如何推广网站

如何做一个免费的网站,百度如何推广网站,粘土做龙网站视频,网站信息化建设具体内容存储优化(protobuf与mmkv) 在Android应用开发中,数据存储是一个基础且关键的环节。随着应用功能的日益复杂,数据量的增加,传统的存储方式如SharedPreferences、SQLite等在性能上的局限性逐渐显现。本文将深入探讨两种…

存储优化(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.dtcms.com/wzjs/186065.html

相关文章:

  • 试用网站建设免费手游推广平台
  • 增城免费网站建设贴吧引流推广
  • 做网站平台客服有什么好网站的宣传与推广
  • 仿小米 wordpressseo网络推广软件
  • 小程序怎么开发自己的小程序要钱吗合肥seo管理
  • 上饶市建设监督网站百度自动优化
  • 企业信息的网站日本预测比分
  • 金华网站制作建设郑州seo培训
  • 英文b2b网站系统我要登录百度
  • 擦边球网站做国内还是国外好友情链接适用网站
  • 公司网站做的一样算不算侵权网站网络排名优化方法
  • web前端页面设计seo是什么意思如何实现
  • 政府采购电子商城网站企业网站建设哪家好
  • 企业网站模板 下载 免费seo服务深圳
  • saas系统怎么读河源seo
  • 有后天管理的网站怎么建设seo上海推广公司
  • 云主机做网站2022千锋教育培训收费一览表
  • 莱芜亓家网站国内做网站的公司
  • 如何做网站开发什么叫做网络营销
  • 自己做下载类网站公众号软文是什么意思
  • wordpress好用中文插件广州关键词优化外包
  • 个人网站备案不能盈利河北关键词seo排名
  • 石家庄建设集团有限公司网站广西壮族自治区人民医院
  • 单页网站怎么优化百度云服务器官网
  • 网站做自己的超链接可以免费投放广告的平台
  • 推荐常州网站建设广州推广工具
  • 华人频道青岛网站seo黑帽教学网
  • 网站搜索功能怎么做9 1短视频安装
  • 如何推广游戏优化大师下载安装免费
  • 云南域名注册网站建设百度seo详解