MMKV(1)

2023-10-19 09:20
文章标签 mmkv

本文主要是介绍MMKV(1),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 内存准备

通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。

  • 数据组织

数据序列化方面选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。考虑到要提供的是通用 kv 组件,key 可以限定是 string 字符串类型,value 则多种多样(int/bool/double 等)。要做到通用的话,考虑将 value 通过 protobuf 协议序列化成统一的内存块(buffer),然后就可以将这些 KV 对象序列化到内存中。

message KV {string key = 1;buffer value = 2;
}-(BOOL)setInt32:(int32_t)value forKey:(NSString*)key {auto data = PBEncode(value);return [self setData:data forKey:key];
}-(BOOL)setData:(NSData*)data forKey:(NSString*)key {auto kv = KV { key, data };auto buf = PBEncode(kv);return [self write:buf];
}
  • 写入优化

考虑到主要使用场景是频繁地进行写入更新,需要有增量更新的能力。考虑将增量 kv 对象序列化后,append 到内存末尾。标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。

  • 空间增长

使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。需要在性能和空间上做个折中。

以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;

排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。

-(BOOL)append:(NSData*)data {if (space >= data.length) {append(fd, data);} else {newData = unique(m_allKV);if (total_space >= newData.length) {write(fd, newData);} else {while (total_space < newData.length) {total_space *= 2;}ftruncate(fd, total_space);write(fd, newData);}}
}
  • 数据有效性

考虑到文件系统、操作系统都有一定的不稳定性, crc 校验,对无效数据进行甄别。在 iOS 微信现网环境上,有平均约 70万日次的数据校验不通过。

  • Android多进程访问

将 MMKV 迁移到 Android 平台之后,要支持多进程访问, iOS 不支持多进程。

  1. 性能:MMKV在性能方面表现更好。由于采用了内存映射技术,它可以直接在内存中读取和写入数据,减少了磁盘IO操作,因此读写速度更快。相比之下,SharedPreferences是基于XML文件存储的,读取和写入需要进行磁盘IO操作,速度较慢。

  2. 跨进程和跨线程支持:MMKV天然支持跨进程和跨线程的数据共享。多个进程或线程可以同时访问和修改MMKV中的数据,而无需额外的同步操作。而SharedPreferences的跨进程支持较差,需要进行额外的同步机制或使用ContentProvider等方式才能实现跨进程共享。

  3. 存储容量:MMKV支持更大的存储容量。SharedPreferences将所有数据都存储在一个XML文件中,如果数据较多,读取和解析整个文件可能会影响性能。而MMKV将数据划分为多个固定大小的内存页,可以高效地读取和写入大量数据。

  4. 序列化和加密:MMKV提供了数据的序列化和加密功能。它可以将复杂的数据结构序列化为字节数组进行存储,并支持对数据进行加密保护。而SharedPreferences只能存储基本数据类型,对于复杂的数据结构需要进行手动的序列化和反序列化操作。灵活性和易用性:MMKV提供了更灵活和易用的API。它的API设计更加简洁,使用起来更方便,支持链式调用和类型安全。同时,MMKV还提供了一些额外的功能,如数据的版本控制、数据迁移和备份等。

  5. import com.tencent.mmkv.MMKV;public class MMKVExample {public static void main(String[] args) {// 初始化MMKVString rootDir = MMKV.initialize("path_to_directory");// 获取MMKV实例MMKV mmkv = MMKV.defaultMMKV();// 存储数据mmkv.putString("key1", "value1");mmkv.putInt("key2", 123);mmkv.putBoolean("key3", true);// 读取数据String value1 = mmkv.getString("key1", "");int value2 = mmkv.getInt("key2", 0);boolean value3 = mmkv.getBoolean("key3", false);System.out.println("Value 1: " + value1);System.out.println("Value 2: " + value2);System.out.println("Value 3: " + value3);}
    }
    dependencies {implementation 'com.tencent:mmkv:1.0.23'// replace "1.0.23" with any available version
    }

    支持从SP迁移数据importFromSharedPreferences

    MMKV 还额外实现了一遍 SharedPreferences、SharedPreferences.Editor 这两个 interface。

    // 可以跟SP用法一样
    SharedPreferences.Editor editor = mmkv.edit();
    // 无需调用 commit()
    //editor.commit();

    MMKV 的使用非常简单,所有变更立马生效,无需调用 sync、apply。 在 App 启动时初始化 MMKV,设定 MMKV 的根目录(files/mmkv/),例如在 MainActivity 里:

    protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);String rootDir = MMKV.initialize(this);System.out.println("mmkv root: " + rootDir);//……
    }

    MMKV 提供一个全局的实例,可以直接使用:

    import com.tencent.mmkv.MMKV;
    //……MMKV kv = MMKV.defaultMMKV();kv.encode("bool", true);
    boolean bValue = kv.decodeBool("bool");kv.encode("int", Integer.MIN_VALUE);
    int iValue = kv.decodeInt("int");kv.encode("string", "Hello from mmkv");
    String str = kv.decodeString("string");

    使用后:

public native void clearAll();// MMKV's size won't reduce after deleting key-values// call this method after lots of deleting f you care about disk usage// note that `clearAll` has the similar effect of `trim`public native void trim();// call this method if the instance is no longer needed in the near future// any subsequent call to the instance is undefined behaviorpublic native void close();// call on memory warning// any subsequent call to the instance will load all key-values from file againpublic native void clearMemoryCache();// you don't need to call this, really, I mean it// unless you care about out of batterypublic void sync() {sync(true);}
  • 限制

一个键会存入多分实例,最后存入的就是最新的。

MMKV 在大部分情况下都性能强劲,key/value 的数量和长度都没有限制。

然而 MMKV 在内存里缓存了所有的 key-value,在总大小比较大的情况下(例如 100M+),App 可能会爆内存,触发重整回写时,写入速度也会变慢。

锁 lock unlock tryLock

注意如果一个进程lock住,另一个进程mmkvWithID获取MMKV时就阻塞住,直到持有进程释放。

// get the lock immediatelyMMKV mmkv2 = MMKV.mmkvWithID(LOCK_PHASE_2, MMKV.MULTI_PROCESS_MODE);mmkv2.lock();Log.d("locked in child", LOCK_PHASE_2);Runnable waiter = new Runnable() {@Overridepublic void run() {//阻塞住 直到其他进程释放MMKV mmkv1 = MMKV.mmkvWithID(LOCK_PHASE_1, MMKV.MULTI_PROCESS_MODE);mmkv1.lock();Log.d("locked in child", LOCK_PHASE_1);}};

如果其他进程有进行修改,不会立即触发onContentChangedByOuterProcess,

checkLoadData如果变化,会clearMemoryState,重新loadFromFile。//数据量大时不要太频繁

读取decodeXXX会阻塞住,先回调onContentChangedByOuterProcess,再返回值,保证值是最新的。

  • Binder MMAP

Binder MMAP是Android系统中的一种机制,用于在跨进程通信(IPC)中传输大型数据或共享内存区域。

在Android中,进程间通信主要通过Binder框架实现。Binder框架使用Binder驱动程序在不同的进程之间建立通信通道。通常情况下,进程间通信是通过传输小型的数据结构,如整数、字符串等。然而,当需要传输大量数据或者共享内存时,效率会受到限制。

为了解决这个问题,Android引入了Binder MMAP机制。Binder MMAP允许进程通过内存映射(MMAP)的方式共享内存区域,从而实现高效的数据传输。它通过以下步骤实现:

  1. 发送端将数据写入内存区域:发送端将要传输的数据写入一个内存区域,该内存区域通过MMAP映射到物理内存中。

  2. 发送端将内存区域的描述符发送给接收端:发送端将内存区域的描述符(文件描述符)通过Binder传递给接收端。

  3. 接收端获取内存区域描述符并映射到自己的地址空间:接收端通过Binder接收内存区域的描述符,并将其映射到自己的地址空间。

  4. 接收端从内存区域读取数据:接收端可以直接从内存区域中读取发送端写入的数据,而无需进行数据拷贝。

Binder MMAP的优点:

  1. 高效的数据传输:通过内存映射的方式,避免了数据拷贝和序列化/反序列化操作,提高了数据传输的效率。

  2. 支持大型数据和共享内存:适用于传输或共享大量数据或大型内存区域的场景,可以减少内存占用和提高性能。

  3. 跨进程通信:作为Android的进程间通信机制,支持不同进程之间的数据传输,方便实现跨进程功能。

Binder MMAP的缺点:

  1. 复杂性:相比较其他传输方式,使用Binder MMAP需要更多的代码和配置,对开发者来说可能需要更多的学习和理解。

  2. 依赖Binder框架:作为Android系统的一部分,使用Binder MMAP需要依赖于Binder框架,需要遵循Binder框架的规范和约束。

MMKV的优点:

  1. 高性能:相对于SharedPreferences等传统存储方式,MMKV具有更好的读写性能,特别是在高并发操作下表现更出色。

  2. 跨进程支持:MMKV支持跨进程访问,多个进程可以同时读写同一个MMKV实例,方便实现进程间数据共享。

  3. 功能丰富:MMKV提供了丰富的功能,如加密、压缩等,可以满足不同的数据存储需求。

MMKV的缺点:

  1. 存储大小限制:MMKV存储的总大小受限于设备的存储空间,如果存储的数据量较大,可能会占用较多的存储空间。

  2. 适用性受限:由于MMKV是针对键值存储而设计的,适用于存储简单的键值对数据,不适合存储复杂的数据结构。

  • 比sp快的原因

  1. 内存映射(Memory Mapping):MMKV使用了内存映射的技术,将数据直接映射到内存中,而不是像SharedPreferences一样将数据写入磁盘文件。这种内存映射的方式避免了频繁的磁盘读写操作,减少了IO开销,从而提高了读写性能。

  2. 零拷贝(Zero-copy):MMKV利用了内存映射的特性,实现了零拷贝的读写操作。当读取或写入数据时,MMKV直接在内存中进行操作,避免了数据的拷贝和序列化/反序列化操作,进一步提高了读写性能。

  3. 文件锁(File Locking):MMKV使用文件锁机制来保证多个进程对同一个MMKV实例的安全访问。这种文件锁机制可以有效地控制并发访问,避免了数据冲突和竞争条件,提高了并发操作的性能。

  4. 自定义序列化(Custom Serialization):MMKV使用自定义的序列化方式,将数据以二进制的形式存储,而不是SharedPreferences中的XML格式。这种自定义的序列化方式更加高效,减少了存储和解析数据的开销,提高了读写性能。

  • MMKV在android中的使用(替换sp)

在项目根目录下的 build.gradle 文件中加入

dependencies {implementation 'com.tencent:mmkv-static:1.2.10'
}

在项目 app 模块下的 build.gradle 文件中加入 

buildscript {repositories {mavenCentral()//这行依赖}
}
allprojects {repositories {mavenCentral()//这行依赖}
}

在Application中初始化

MMKV.initialize(this)

MMKV 默认把文件存放在$(FilesDir)/mmkv/目录。你可以在 MMKV初始化时自定义根目录:

String dir = getFilesDir().getAbsolutePath() + "/mmkv";
String rootDir = MMKV.initialize(dir);

Kotlin中使用

import com.tencent.mmkv.MMKV;
//……//1. 获取默认全局实例 (与下面的几选一,一般就使用这个就行)
var mmkv: MMKV = MMKV.defaultMMKV()//2. 也可以自定义MMKV对象,设置自定ID  (根据业务区分的存取实例)
var mmkv: MMKV = MMKV.mmkvWithID("ID")//3. MMKV默认是支持单进程的,如果业务需要多进程访问,需要在初始化的时候添加多进程模式参数
var mmkv = MMKV.mmkvWithID("ID", MMKV.MULTI_PROCESS_MODE)  //多进程同步支持

存取方法

// 添加/更新数据
mmkv?.encode(key, value);// 获取数据
int value = mmkv.decodeInt(key);
String value = mmkv.decodeString(key);
//...获取等类型// 删除数据
mmkv.removeValueForKey(key);

如果需要存取对象,可以用存取对象json字符串的方法,将对象转成json存,取出json转回对象。

SP迁移

MMKV可以调用importFromSharedPreferences方法进行SP的数据迁移,示例代码如下: MMKV实现了SharedPreferences,Editor两个接口,所以在迁移之后SP的操作代码可以不用更改。

val mmkv = MMKV.mmkvWithID("myData")
val olderData = DemoApplication.mContext?.getSharedPreferences("myData", MODE_PRIVATE)
mmkv?.importFromSharedPreferences(olderData)
olderData?.edit()?.clear()?.apply()
  • Java中的使用

MMKV 提供一个全局的实例,可以直接使用

import com.tencent.mmkv.MMKV;
//……//1. 获取默认全局实例 (一般就使用这个就行)
MMKV kv = MMKV.defaultMMKV();//2. 也可以自定义MMKV对象,设置自定ID  (根据业务区分的存取实例)
MMKV kv = MMKV.mmkvWithID("ID");//3. MMKV默认是支持单进程的,如果业务需要多进程访问,需要在初始化的时候添加多进程模式参数
MMKV kv = MMKV.mmkvWithID("ID", MMKV.MULTI_PROCESS_MODE); //多进程同步支持

存取方法

/** 添加/更新数据 **/
//存boolean类型
kv.encode("bool", true);
//存int类型
kv.encode("int", Integer.MIN_VALUE);
//存string类型
kv.encode("string", "MyiSMMKV");/** 获取数据 **/
//获取boolean类型数据
boolean bValue = kv.decodeBool("bool");
//获取int类型数据
int iValue = kv.decodeInt("int");
//获取string类型数据
String str = kv.decodeString("string");
//...等类型的获取// 删除数据
mmkv.removeValueForKey(key);

如果需要存取对象,可以用存取对象json字符串的方法,将对象转成json存,取出json转回对象。

SP迁移

MMKV kv = MMKV.mmkvWithID("myData");
SharedPreferences olderData = App.getInstance().getSharedPreferences("myData", MODE_PRIVATE);
kv.importFromSharedPreferences(olderData);
olderData.edit().clear().apply();

这篇关于MMKV(1)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/238862

相关文章

Android经典实战之存储方案对比:SharedPreferences vs MMKV vs DataStore

本文首发于公众号“AntDream”,欢迎微信搜索“AntDream”或扫描文章底部二维码关注,和我一起每天进步一点点 在 Android 开发中,键值对存储(Key-Value Storage)是一种经常用到的轻量级数据存储方案。它主要用于保存一些简单的配置数据或状态信息,例如用户设置、缓存数据等。 常见的键值对存储方案 1、 SharedPreferences: 一个轻量级的持久

Android 常用开源库 MMKV 源码分析与理解

文章目录 前言一、MMKV简介1.mmap2.protobuf 二、MMKV 源码详解1.MMKV初始化2.MMKV对象获取3.文件摘要的映射4.loadFromFile 从文件加载数据5.数据写入6.内存重整7.数据读取8.数据删除9.文件回写10.Protobuf 实现1.序列化2.反序列化 12.文件锁1.加锁2.解锁 13.状态同步 总结参考文献 前言 谈到轻量级的数

android ——MMKV保存bean实体对象、保存数组bean

MMKV简介 MMKV是一款基于 mmap 内存映射的 key-value 存储库,专门为移动端开发设计。相比于传统的SharedPreferences和SQLite等存储方式,MMKV具有更快的读写速度和更小的存储空间占用。同时,MMKV还提供了多线程安全的支持和可配置的加密选项。因此,在移动端开发中,MMKV被广泛应用于数据持久化和缓存优化等方面。 添加依赖 implementat

SharedPreferences垃圾吗?对比MMKV和DataStore经验之谈

SharedPreferences 很垃圾吗? 嗯,他会阻塞主线程。他可能会崩溃,他可能无法提供大内容的存储,性能比较差,ANR等等。 但是是它的错吗?他的设计本意是提供极少的一些变量存储。结果臃肿的代码和封装写法,过度使用导致了很多问题。 如果不想看全篇,看粗体内容看看你是否有共鸣和不了解的地方,查漏补缺。 目前流行的存储有如下几个我这边自行给出个人使用感受: MMKV 通过内存映

Android MMKV 接入+ 替换原生 SP + 原生 SP 数据迁移

背景:项目中一直使用的是原生 SP,众所周知,使用原生 SP 存在卡顿性能问题。公司的性能监控平台抓到不少原生 SP 导致的 ANR 问题: java.io.FileDescriptor.sync (FileDescriptor.java)android.os.FileUtils.sync (FileUtils.java:256)android.app.SharedPreferencesI

MMKV 基于 mmap 的高性能通用 key-value 组件

学前介绍 MMKV 是基于 mmap 内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。从 2015 年中至今,在 iOS 微信上使用已有近 3 年,其性能和稳定性经过了时间的验证。 官方MMKV地址 为什么要替代SharedPreferences? 1,数据加密。 在 Android 环境里,数据加密是非常必须的,SP实际上是

SharedPreference进阶MMKV框架

SharedPreference 简介 顾名思义,Android 开发肯定会使用到SharedPreference。以下简称SP sp是Android平台上一个轻量级的存储类,用来保存应用的一些常用配置。 是以xml文件保到:/data/data/包名/shared_prefs目录下。类似键值对的方式来存储数据。 sp提供了常规的数据类型保存接口比如:int、long、boolean、St

ZUI易入门Android之 MMKV

MMKV是什么?      在Android开发过程中,我们经常会使用到一些存储。经常使用sharepreferences存储,当你的数据量在不需要使用数据库,但sharepreferences感觉又无法承载的时候,但性能卓越的存储框架,由腾讯旗下的微信开发—MMKV 今天我们主要从这几个方面来解释一下MMKV MMKV存储优势MMKV存储支持的数据类型MMKV引入依赖MMKV存储与

web前端开发培训心得,MMKV集成与原理

form表单细节 一、表单 1.表单 标签用于为用户输入创建 HTML 表单 2.表单能够包含 input 元素,比如文本字段、复选框、单选框、提交按钮等等。 3.表单还可以包含 menus、textarea、fieldset、legend 和 label 元素。 4.表单用于向服务器传输数据。 二、表单form 的属性 (一)action属性 action

MMKV(3)

使用时遇到的问题 在项目的构建配置文件(如 Gradle 或 Maven)中添加相应的依赖项。 MMKV 是一个键值存储库,它存储的是原始的字节数组数据。需要存储和检索复杂的对象或数据结构,需要自行进行序列化和反序列化操作。可以使用任何合适的序列化库(如 JSON、Protocol Buffers 等)将对象转换为字节数组,并在读取时进行相反的操作。 读取数据时,需要将字节数组转换回相应的数