iOS进阶——微信开源存储框架MMKV(一)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS进阶——微信开源存储框架MMKV(一)相关的知识,希望对你有一定的参考价值。
参考技术A MMKV是微信开源的数据持久化框架,现在已经支持android/ios/PC 平台。该框架是基于mmap映射内存的key—value组件,使用protobuf实现数据的序列化和反序列化,性能高,稳定性强。微信在2015就在微信应用上使用了该框架。实验证明MMKV是数据持久化的首选。ProtoBuf是由google公司用于数据交换的序列结构化数据格式,具有跨平台、跨语言、可扩展特性,类型于常用的XML及JSON,但具有更小的传输体积、更高的编码、解码能力,特别适合于数据存储、网络数据传输等对存储体积、实时性要求高的领域。
优点:空间效率搞,时间效率要高,对于数据大小敏感,传输效率高的。
缺点:消息结构可读性不高,目前使用不广泛。
MMKV维护了一个<String,AnyObject>的dic,在写入数据时,会在dit和mmap映射区写入相同的数据,最后由内核同步到文件。因为dic和文件数据同步,所以读取时直接去dit中的值。MMKV数据持久化的步骤:mmap 内存映射 -> 写数据 -> 读数据 -> crc校验 -> aes加密。
在MMKV的源码中,是怎么样内存映射的呢?
微信开源库MMKV遍历读取存储的所有key以及对应的value方法
微信开源库MMKV遍历读取存储的所有key以及对应的value方法
最近正在使用微信的开源库MMKV,替代项目中已存在的sharePreferences,替换过程非常简单,使用MMKV的导入SP接口直接把SP里面的数据全部转移到MMKV中。
项目中存在一个测试工具,读取SP文件里面的所有key以及对应的value在recyclerView中进行展示,方便查看和修改。在更新MMKV之后由于存储方式的改变,无法通过旧代码读取文件遍历所有数据,需要更新遍历的逻辑。
MMKV提供读取所有key集合的api:mmkv.allKeys()
通过遍历所有key读取value
到了decode这一步,需要判断value的类型确定使用哪个api进行decode数据。由于MMKV存储格式原因,读取value的时候无法像以前SP那样直接读出Object然后进行类型判断。在MMKV里面不管encode什么类型的数据,通过其他类型的api decode时都不会出现异常,也不会读取出错返回default值,所以判断类型这一步,需要观察数据读取的规律进行逻辑判断。
对各种基本类型数据进行encode并通过不同的decode读取
string-set: [1, 2, 3]
D/PrefEditFragment: testMMKV: decodeInt 6
D/PrefEditFragment: testMMKV: decodeFloat 3.25105E-38
D/PrefEditFragment: testMMKV: decodeDouble 1.057169819026035E-307
D/PrefEditFragment: testMMKV: decodeLong 6
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString <0x01>1<0x01>2<0x01>3
D/PrefEditFragment: testMMKV: decodeBytes <0x01>1<0x01>2<0x01>3
D/PrefEditFragment: testMMKV: decodeStringSet [1, 2, 3]
int: 100
D/PrefEditFragment: testMMKV: decodeInt 100
D/PrefEditFragment: testMMKV: decodeFloat 1.4E-43
D/PrefEditFragment: testMMKV: decodeDouble 4.94E-322
D/PrefEditFragment: testMMKV: decodeLong 100
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet null
long: 100
D/PrefEditFragment: testMMKV: decodeInt 100
D/PrefEditFragment: testMMKV: decodeFloat 1.4E-43
D/PrefEditFragment: testMMKV: decodeDouble 4.94E-322
D/PrefEditFragment: testMMKV: decodeLong 100
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet null
float: 0
D/PrefEditFragment: testMMKV: decodeInt 0
D/PrefEditFragment: testMMKV: decodeFloat 0.0
D/PrefEditFragment: testMMKV: decodeDouble 0.0
D/PrefEditFragment: testMMKV: decodeLong 0
D/PrefEditFragment: testMMKV: decodeBool false
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet []
long: Long.MAX_VALUE-1
D/PrefEditFragment: testMMKV: decodeInt -2
D/PrefEditFragment: testMMKV: decodeFloat NaN
D/PrefEditFragment: testMMKV: decodeDouble NaN
D/PrefEditFragment: testMMKV: decodeLong 9223372036854775806
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet null
double: 100d
D/PrefEditFragment: testMMKV: decodeInt 0
D/PrefEditFragment: testMMKV: decodeFloat 0.0
D/PrefEditFragment: testMMKV: decodeDouble 100.0
D/PrefEditFragment: testMMKV: decodeLong 0
D/PrefEditFragment: testMMKV: decodeBool false
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet []
float: 100f
D/PrefEditFragment: testMMKV: decodeInt 0
D/PrefEditFragment: testMMKV: decodeFloat 100.0
D/PrefEditFragment: testMMKV: decodeDouble 5.53552857E-315
D/PrefEditFragment: testMMKV: decodeLong 0
D/PrefEditFragment: testMMKV: decodeBool false
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet []
bool: true
D/PrefEditFragment: testMMKV: decodeInt 1
D/PrefEditFragment: testMMKV: decodeFloat 1.4E-45
D/PrefEditFragment: testMMKV: decodeDouble 4.9E-324
D/PrefEditFragment: testMMKV: decodeLong 1
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet null
bool: false
D/PrefEditFragment: testMMKV: decodeInt 0
D/PrefEditFragment: testMMKV: decodeFloat 0.0
D/PrefEditFragment: testMMKV: decodeDouble 0.0
D/PrefEditFragment: testMMKV: decodeLong 0
D/PrefEditFragment: testMMKV: decodeBool false
D/PrefEditFragment: testMMKV: decodeString
D/PrefEditFragment: testMMKV: decodeBytes
D/PrefEditFragment: testMMKV: decodeStringSet null
string: 嘿嘿嘿
D/PrefEditFragment: testMMKV: decodeInt 9
D/PrefEditFragment: testMMKV: decodeFloat -1.1944896
D/PrefEditFragment: testMMKV: decodeDouble -1.3111330438961331E182
D/PrefEditFragment: testMMKV: decodeLong 9
D/PrefEditFragment: testMMKV: decodeBool true
D/PrefEditFragment: testMMKV: decodeString 嘿嘿嘿
D/PrefEditFragment: testMMKV: decodeBytes 嘿嘿嘿
D/PrefEditFragment: testMMKV: decodeStringSet []
根据上面的数据读取规律,优先处理string
和string-set
类型,因为只有string
和string-set
类型encode才能通过同样的decode读出来,而其他类型通过string
进行decode出来的都是空字符串。再观察string
和string-set
类型的区别,可以看到decode成string
的string-set
数据是包含<0x01>
作为分隔符的。当字符串以<0x01>
开头时,我们可以把类型解析为string-set
,否则解析为string
,如果encode的字符串确实以<0x01>
开头,那就要开发者根据自己的业务规则自行处理了。
继续根据数据读取规律,接下来把 int、long、bool
类型放到一起处理,这里可以看到 int
类型1
和0
等价于 bool
类型true
和false
。所以bool
类型可以等价于int
类型进行处理。然后看int
类型与long
类型,它们分别是32位和64位,所以当值是小于32位时,可以直接当成int
读取,因为两种类型的值是相等的;当两种类型的值不相等时,说明值超出了32位的范围,可以直接用long
类型读取。
最后剩下float
和double
类型,这两种类型就比较难处理了,在处理这两种数据类型之前,先去了解了float类型和double类型的二进制存储,这里简单记录一下:
float
类型是32位的,它的组成是 1个符号位,8个阶码位,23个尾数位
double
类型是64位的,它的组成是 1个符号位,11个阶码位,52个尾数位
举个栗子,存储一个 100f 的float
数据,把它转成byte[]
的形式看,表示4个字节[66, -56, 0, 0]
。
而把这个 100f 的float
数据转成double
类型的byte[]
形式,表示8个字节[0, 0, 0, 0, 66, -56, 0, 0]
。
可以看到当一个float
类型数据通过double
类型读出时,数据存储在64位8个字节中的后32位。
由于float
类型只有32位,所以无论任何时候一个float
数据读到8个字节里面,前面32位均为0。但条件反过来,把数据读到64位8个字节里,前32位均为0并不代表这个数据就是float
数据。
当double
类型的数据处于[0, 0, 0, 0, 0, 0, 0, 0]
~ [0, 0, 0, 0, -127, -127, -127, -127]
的时候,我们无法判断这是一个float
类型数据还是double
类型数据。
//这个数据范围用指数的形式表示就是
0 < value <= 1.0569021313E-314
//说人话,数据范围大概是
0 < value <= 0.0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000105690213213d
只要存储的double类型数据不在这个范围,那就可以用上面提到的条件进行类型判断:把数据用double
类型读出来,前32位均为0就把它当做float
数据。(如果存储的数据确实在上面说的范围内,则需要根据业务逻辑自行处理类型判断)
自动判断类型并获取value的方法代码:
private Object getObjectValue(MMKV mmkv, String key)
// 因为其他基础类型value会读成空字符串,所以不是空字符串即为string or string-set类型
String value = mmkv.decodeString(key);
if (!TextUtils.isEmpty(value))
// 判断 string or string-set
if (value.charAt(0) == 0x01)
return mmkv.decodeStringSet(key);
else
return value;
// float double类型可通过string-set配合判断
// 通过数据分析可以看到类型为float或double时string类型为空字符串且string-set类型读出空数组
// 最后判断float为0或NAN的时候可以直接读成double类型,否则读float类型
// 该判断方法对于非常小的double类型数据 (0d < value <= 1.0569021313E-314) 不生效
Set<String> set = mmkv.decodeStringSet(key);
if (set != null && set.size() == 0)
Float valueFloat = mmkv.decodeFloat(key);
Double valueDouble = mmkv.decodeDouble(key);
if (Float.compare(valueFloat, 0f) == 0 || Float.compare(valueFloat, Float.NaN) == 0)
return valueDouble;
else
return valueFloat;
// int long bool 类型的处理放在一起, int类型1和0等价于bool类型true和false
// 判断long或int类型时, 如果数据长度超出int的最大长度, 则long与int读出的数据不等, 可确定为long类型
int valueInt = mmkv.decodeInt(key);
long valueLong = mmkv.decodeLong(key);
if (valueInt != valueLong)
return valueLong;
else
return valueInt;
以上是关于iOS进阶——微信开源存储框架MMKV(一)的主要内容,如果未能解决你的问题,请参考以下文章