前言:由于知識(shí)點(diǎn)多稽坤,分了多個(gè)記錄倦沧。
MMKV( 一) 了解原理
MMKV (二)基礎(chǔ)知識(shí)點(diǎn)和實(shí)現(xiàn)流程解析
MMKV (三) POSIX線程和文件鎖
導(dǎo)言:MMKV 可以多進(jìn)程通信,實(shí)際上就是共享文件的方式,是基于 mmap 內(nèi)存映射的 key-value 組件牲尺,底層序列化/反序列化使用 protobuf 實(shí)現(xiàn)乘寒,性能高,穩(wěn)定性強(qiáng)万栅,可以跨進(jìn)程
和SharePreference 對(duì)比
SharePreference 源碼實(shí)現(xiàn) SharePreferenceImpl.java 對(duì)xml的讀寫(xiě)
不能跨進(jìn)程佑钾,如果非要跨進(jìn)程配合ContentProvider使用。
讀寫(xiě)方式:I/O
數(shù)據(jù)格式:XML
k-v 更新:全量更新,n個(gè)k-v->序列化成xml io->文件
MMKV 對(duì)比
I/O vs mmap
數(shù)據(jù)格式:
1烦粒、總體結(jié)構(gòu)
2休溶、整型編碼
3、計(jì)算長(zhǎng)度
增量與全量寫(xiě)入
mmap 和 mmap的優(yōu)缺點(diǎn)
Linux通過(guò)將一個(gè)虛擬內(nèi)存區(qū)域與一個(gè)磁盤(pán)上的對(duì)象關(guān)聯(lián)起來(lái)扰她,以初始化這個(gè)虛擬內(nèi)存區(qū)域的內(nèi)容兽掰,這個(gè)過(guò)程稱為內(nèi)存映射(memory mapping)。
對(duì)文件進(jìn)行mmap徒役,會(huì)在進(jìn)程的虛擬內(nèi)存分配地址空間孽尽,創(chuàng)建映射關(guān)系
實(shí)現(xiàn)這樣的映射關(guān)系后,就可以采用指針的方式讀寫(xiě)操作這一段內(nèi)存忧勿,而系統(tǒng)會(huì)自動(dòng)回寫(xiě)到對(duì)應(yīng)的文件磁盤(pán)上
I/O 在系統(tǒng)的操作流程
虛擬內(nèi)存被操作系統(tǒng)劃分成兩塊:用戶空間和內(nèi)核空間杉女,用戶空間是用戶程序代碼運(yùn)行的地方艇拍,內(nèi)核空間是內(nèi)核代碼運(yùn)行的地方。為了安全宠纯,它們是隔離的卸夕,即使用戶的程序崩潰了,內(nèi)核也不受影響婆瓜。
寫(xiě)文件流程:
1快集、調(diào)用write,告訴內(nèi)核需要寫(xiě)入數(shù)據(jù)的開(kāi)始地址與長(zhǎng)度
2廉白、內(nèi)核將數(shù)據(jù)拷貝到內(nèi)核緩存
3个初、由操作系統(tǒng)調(diào)用,將數(shù)據(jù)拷貝到磁盤(pán)猴蹂,完成寫(xiě)入
優(yōu)點(diǎn)
- MMAP對(duì)文件的讀寫(xiě)操作只需要從磁盤(pán)到用戶主存的一次數(shù)據(jù)拷貝過(guò)程院溺,減少了數(shù)據(jù)的拷貝次數(shù),提高了文件讀寫(xiě)效率磅轻。
- MMAP使用邏輯內(nèi)存對(duì)磁盤(pán)文件進(jìn)行映射珍逸,操作內(nèi)存就相當(dāng)于操作文件,不需要開(kāi)啟線程聋溜,操作MMAP的速度和操作內(nèi)存的速度一樣快谆膳;
- MMAP提供一段可供隨時(shí)寫(xiě)入的內(nèi)存塊,App 只管往里面寫(xiě)數(shù)據(jù)撮躁,由操作系統(tǒng)如內(nèi)存不足漱病、進(jìn)程退出等時(shí)候負(fù)責(zé)將內(nèi)存回寫(xiě)到文件,不必?fù)?dān)心 crash 導(dǎo)致數(shù)據(jù)丟失把曼。
參考微信和美團(tuán)
微信 Mars : https://mp.weixin.qq.com/s/kDPTt9Rtd-PERXXW-UyUlQ
美團(tuán) Logan : https://tech.meituan.com/2018/02/11/logan.html
防止數(shù)據(jù)丟失并提升了讀寫(xiě)效率杨帽。
寫(xiě)入內(nèi)存 | 寫(xiě)入mmap | 寫(xiě)入文件 | |
---|---|---|---|
OS X | 17ms | 18ms | 2685ms |
iOS | 55ms | 55ms | 13116ms |
Android | 283ms | 2488ms | 7583ms |
三大優(yōu)勢(shì)
mmap防止數(shù)據(jù)丟失,提高讀寫(xiě)效率;
精簡(jiǎn)數(shù)據(jù)嗤军,以最少的數(shù)據(jù)量表示最多的信息注盈,減少數(shù)據(jù)大小;
增量更新,避免每次進(jìn)行相對(duì)增量來(lái)說(shuō)大數(shù)據(jù)量的全量寫(xiě)入
缺點(diǎn)
因?yàn)槲覀兪前凑枕?yè)存儲(chǔ)方式型雳,每頁(yè)4096字節(jié)当凡,我們只有100字節(jié)使用,那么整個(gè)頁(yè)4096字節(jié)映射纠俭,所以造成一定的浪費(fèi)
MMKV 存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)
對(duì)比xml 結(jié)構(gòu)
整型編碼
1個(gè)字節(jié)保存7位數(shù)據(jù)
如果寫(xiě)入的數(shù)據(jù) <= 0x7f 那么使用7位即1個(gè)字節(jié)表示沿量。
如果寫(xiě)入的數(shù)據(jù) > 0x7f 那么先記錄低7位數(shù)據(jù),并將最高位設(shè)為1冤荆,繼續(xù)執(zhí)行判斷
寫(xiě)入方式
增量寫(xiě)入:
不管key是否重復(fù)朴则,直接將數(shù)據(jù)追加在前數(shù)據(jù)后。
如果不斷 增量 追加內(nèi)容钓简,文件越來(lái)越大乌妒,怎么辦汹想?
全量寫(xiě)入:
當(dāng)文件大小不夠,這時(shí)候需要全量寫(xiě)入撤蚊。將數(shù)據(jù)去掉重復(fù)key后古掏,如果文件大小滿足寫(xiě)入的數(shù)據(jù)大小,則可以直接更新全量寫(xiě)入侦啸,否則需要擴(kuò)容槽唾。(在擴(kuò)容時(shí)根據(jù)平均每個(gè)K-V大小計(jì)算未來(lái)可能需要的文件大小進(jìn)行擴(kuò)容,防止經(jīng)常性的全量寫(xiě)入)
擴(kuò)容
擴(kuò)容非常簡(jiǎn)單光涂,只需要重新設(shè)定文件大小庞萍,然后重新mmap映射即可
//重新設(shè)定文件大小
ftruncate(m_fd, m_size);
//解除映射
munmap(m_ptr, oldSize);
//重新映射
m_ptr = (int8_t *) mmap(m_ptr, m_size, PROT_READ | PROT_WRITE, MAP_SHARED, m_fd, 0);
存儲(chǔ)的小例子
010 編輯器查看截圖,總長(zhǎng)度-》key長(zhǎng)度-》value長(zhǎng)度
沒(méi)有xml 的冗余數(shù)據(jù)忘闻,更小钝计,更方便增量更新∑爰眩總長(zhǎng)度改下私恬,k-v 在后面追加就可以了。內(nèi)存中的map集合中永遠(yuǎn)是最新的數(shù)據(jù)重虑。
Protobuf 變長(zhǎng)編碼 例子
編碼65280-》
1111 1111 0000 0000
取低七位:000 0000践付,最高位補(bǔ)1(表示還需要下一個(gè)字節(jié))
1000 0000 -》寫(xiě)出
右移7位:1111 1111 0
取低七位:111 1110秦士,最高位補(bǔ)1
1111 1110 -》 寫(xiě)出
右移7位:11
小于0x7f缺厉,最高位不需要設(shè)置為1
0000 0011 -》寫(xiě)出
解析:
1000 0000
1111 1110
0000 0011
拿后7位,7個(gè)0
000 0000
高位是1需要繼續(xù)獲取
再次拿后7位
111 1110
高位是1需要繼續(xù)獲取拼接 111 1110 000 0000
再次拿后7位
000 0011
高位0隧土,不許繼續(xù)提针,直接拼接去掉頭部無(wú)用0
000 0011111 1110 000 0000
最后整理之后
1111 1111 0000 0000
被編譯成了3個(gè)字節(jié),定長(zhǎng)編碼應(yīng)該是4個(gè)字節(jié)曹傀,節(jié)省1個(gè)字節(jié)
1頁(yè)4096 字節(jié)辐脖,linux 按照頁(yè)方式存儲(chǔ),超過(guò)1頁(yè)數(shù)據(jù)量發(fā)生頁(yè)中斷皆愉。
參考MMKV 文檔
官方項(xiàng)目MMKV project