MMKV实践与测试 - Xiasm/Java-Android-Learn GitHub Wiki
微信最新开源项目MMKV开源当日即登Github T认定日榜,三日后登上周榜。MMKV是基于内存映射的移动端通用key-value组件,底层序列化反序列化使用protobuf实现,性能高,稳定性强。从2015年中至今,在IOS微信上使用已有近三年,近期移置到Android平台,移动端全平台通用,并全部在Github上开源。(来自官方介绍)
按顺序来,先说使用方式再说性能,最后谈谈它的原理和弊端。(其实官方文档介绍的已经非常清楚了,之所以写这篇博客,是自己做了下总结给懒人看的)
使用方式很简单,作为如今的Android开发,我们只需要通过gradle引入如下:
implementation 'com.tencent:mmkv:1.0.10'
这里需要注意,MMKV支持的 Android minSdkVersion 为16,如果你的项目还在支持Android 4.0,那么就不建议使用了。
使用起来很简单,首先需要初始化MMKV:
MMKV.initialize(this);
然后获取MMKV实例:
MMKV mmkv = MMKV.defaultMMKV();
存储数据就更简单了,只需要调用如下,不用再如prefences一样调用apply或commit:
mmkv.encode("key", "value");
接着我们来获取下存储的信息:
String value = mmkv.decodeString("key", null);
好了,最简单的使用方式已经完了,可以看得出,MMKV的api操作相对SharePerfences来说非常简单,也省下了很多代码!
当然了,MMKV的使用方式还有很多,并且还支持SharePerfences迁移等,这里我直接附上官方的使用教程:
使用指南
如果只是使用便捷,并不能打动我们去替换现有的存储方式,MMKV的性能才是值得尝试它的主要因素。官方文档里介绍了它的性能要比SharePerfences快非常多,作为一个严谨的码农,我们还是实际测试一下更为安全。下面看demo:
我们分别通过SharePerfences和MMKV存储一千条数据,然后对比下两者消耗的时间:
//使用SharePrefences存储一千条key-value
private void saveDataByPrefences() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run:prefences " + Thread.currentThread().getName());
Debug.startMethodTracing("/sdcard/test_prefences");
SharedPreferences preferences = getSharedPreferences("test", MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
for (int i = 0; i < 1000; i++) {
editor.putString(String.valueOf(i), String.valueOf(i));
}
editor.apply();
Debug.stopMethodTracing();
}
}).start();
}
//使用MMKV存储一千条key-value
private void saveDataByMMKV() {
new Thread(new Runnable() {
@Override
public void run() {
Log.d(TAG, "run:mmkv " + Thread.currentThread().getName());
Debug.startMethodTracing("/sdcard/test_mmkv");
MMKV mmkv = MMKV.defaultMMKV();
for (int i = 0; i < 1000; i++) {
mmkv.encode(String.valueOf(i), String.valueOf(i));
}
Debug.stopMethodTracing();
}
}).start();
}
这里我们用Android的 debug trance 方式去测试两种存储消耗的时间,下面看图:
share prefences方式
mmkv方式
上面两图分别为在子线程用shareprefences存储一千条数据和用MMKV存储一千条数据的耗时情况,可以看出,这两中方式在子线程的耗时时间分别为1717183(us)和99637(us),整整相差两个数量级。所以说,在性能上MMKV是要比SharePerfences优越不少的。
无论是单进程还是多进程,操作MMKV都要比操作SharePrefences和SQLite快上很多倍,这里附上官方性能对比:
要说MMKV的限制,就不得不了解它的存储方式。MMKV是通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。下面是官方文档里的写入介绍:“MMKV在序列化的时候采用protobuf协议,标准 protobuf 不提供增量更新的能力,每次写入都必须全量写入。考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力:将增量 kv 对象序列化后,直接 append 到内存末尾;这样同一个 key 会有新旧若干份数据,最新的数据在最后;那么只需在程序启动第一次打开 mmkv 时,不断用后读入的 value 替换之前的值,就可以保证数据是最新有效的。”,从这段话里我们知道了它的增量更新是通过直接将要更新的key-value插入内存末尾,而前面的并不会删除,除非达到了MMKV的内存限制才会进行重排,下面用一个简单的例子演示下更能明白:
//假设 lastActive 属性为统计某一页面的最后打开时间
mmkv.encode("lastActive", 200);
//然后用户不断地打开此页面,就会不断地更新lastActive属性,则MMKV在内存中就会存储多份实例
//假设这些key-value就是存储的数据
<key>lastActive</key><value>200</value>
<key>lastActive</key><value>300</value>
<key>lastActive</key><value>400</value>
//...
<key>lastActive</key><value>1000</value>
<key>lastActive</key><value>2000</value>
可看到,一个键会存入多分实例,最后存入的就是最新的。
那么随之而来的就有另一个问题,就是不断存储的话,文件大小会增长得不可控。例如同一个 key 不断更新的话,是可能耗尽几百 M 甚至上 G 空间,而事实上整个 kv 文件就这一个 key,不到 1k 空间就存得下。这明显是不可取的。我们需要在性能和空间上做个折中:以内存 pagesize 为单位申请空间,在空间用尽之前都是 append 模式;当 append 到文件末尾时,进行文件重整、key 排重,尝试序列化保存排重结果;排重后空间还是不够用的话,将文件扩大一倍,直到空间足够。
所以MMKV的限制我们大家应该很明白了,MMKV 在大部分情况下都性能强劲,key/value 的数量和长度都没有限制。然而MMKV 在内存里缓存了所有的 key-value,在总大小比较大的情况下(例如 100M+),App 可能会爆内存,触发重整回写时,写入速度也会变慢。
MMKV的限制腾讯团队当然很清楚,也正在开发中,所以期待下一个大版本MMKV能有所优化。