MMKV 原理以及使用

介绍

MMKV是基于mmap内存映射的移动端通用key-value组件,底层序列化/反序列化使用protobuf实现,性能高,稳定性强。从2015年中至今,在iOS微信上使用已有近3年,近期移植到Android平台,移动端全平台通用,并全部在Github上开源。

MMKV 原理

内存准备:
  通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。
数据组织:
  数据序列化方面我们选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。考虑到我们要提供的是通用 kv 组件,key 可以限定是 string 字符串类型,value 则多种多样(int/bool/double 等)。要做到通用的话,考虑将 value 通过 protobuf 协议序列化成统一的内存块(buffer),然后就可以将这些 KV 对象序列化到内存中。
写入优化: (重点关注这里!!!)
  标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。
空间增长: (还有这里!!!)
  使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。例如同一个 key 不断更新的话,是可能耗尽几百 M 甚至上 G 空间,而事实上整个 kv 文件就这一个 key,不到 1k 空间就存得下。这明显是不可取的。我们需要在性能和空间上做个折中:以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。
数据有效性:
  考虑到文件系统、操作系统都有一定的不稳定性,我们另外增加了 crc 校验,对无效数据进行甄别。在 iOS 微信现网环境上,我们观察到有平均约 70万日次的数据校验不通过。

MMKV 使用

依赖:

implementation 'com.tencent:mmkv:1.0.19'

在Application里面初始化:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    String rootDir = MMKV.initialize(this);//就这么一句话就行
    System.out.println("mmkv root: " + rootDir);
}

支持的数据类型:

支持以下 Java 语言基础类型:
boolean、int、long、float、double、byte[],String、Set,任何实现了Parcelable的类型,对象存储方式是,转化成json串,通过字符串存储,使用的时候在取出来反序列化.

增:

MMKV kv = MMKV.defaultMMKV();

kv.encode("bool", true);
System.out.println("bool: " + kv.decodeBool("bool"));

kv.encode("int", Integer.MIN_VALUE);
System.out.println("int: " + kv.decodeInt("int"));

kv.encode("long", Long.MAX_VALUE);
System.out.println("long: " + kv.decodeLong("long"));

kv.encode("float", -3.14f);
System.out.println("float: " + kv.decodeFloat("float"));

kv.encode("double", Double.MIN_VALUE);
System.out.println("double: " + kv.decodeDouble("double"));

kv.encode("string", "Hello from mmkv");
System.out.println("string: " + kv.decodeString("string"));

byte[] bytes = {'m', 'm', 'k', 'v'};
kv.encode("bytes", bytes);
System.out.println("bytes: " + new String(kv.decodeBytes("bytes")));

注意:mmkv的写入逻辑是:当我们覆盖某个值的时候,它并不会立即删除前面的值,会保留,然后每个key,value有存储限制,当触发存储限制的时候,才会执行删除,这样即使我们频繁的覆盖,也不会引起太多的性能损耗

删:

MMKV kv = MMKV.defaultMMKV();

kv.removeValueForKey("bool");
System.out.println("bool: " + kv.decodeBool("bool"));
    
kv.removeValuesForKeys(new String[]{"int", "long"});
System.out.println("allKeys: " + Arrays.toString(kv.allKeys()));

改:

直接在存一遍就是.(执行增步骤)

查:

在增的步骤里面,已经打印可查的结果.

 kv.decodeBool("bool");
 kv.decodeInt("int");
.....

如果不同业务需要区别存储,也可以单独创建自己的实例:

MMKV* mmkv = MMKV.mmkvWithID("MyID");
mmkv.encode("bool", true);

SharedPreferences 迁移


    MMKV preferences = MMKV.mmkvWithID("myData");
    // 迁移旧数据
    {
        SharedPreferences old_man = getSharedPreferences("myData", MODE_PRIVATE);
        preferences.importFromSharedPreferences(old_man);
        old_man.edit().clear().commit();
    }
    // 跟以前用法一样
    SharedPreferences.Editor editor = preferences.edit();
    editor.putBoolean("bool", true);
    editor.putInt("int", Integer.MIN_VALUE);
    editor.putLong("long", Long.MAX_VALUE);
    editor.putFloat("float", -3.14f);
    editor.putString("string", "hello, imported");
    HashSet set = new HashSet();
    set.add("W"); set.add("e"); set.add("C"); set.add("h"); set.add("a"); set.add("t");
    editor.putStringSet("string-set", set);
    // 无需调用 commit()
    //editor.commit();

以上内容来自官方github:https://github.com/Tencent/MMKV/wiki/android_setup_cn

你可能感兴趣的:(MMKV 原理以及使用)