美文网首页Android
【学习】MMKV:微信团队开源的轻量级存储方案

【学习】MMKV:微信团队开源的轻量级存储方案

作者: Merbng | 来源:发表于2021-05-14 15:55 被阅读0次

MMKV 轻量级存储方案

定义

  • 基于mmap内存映射的key-value存储组件
  • 是一个类似于SharedPreferences的轻量级存储方案
  • 微信团队开源

优点

  • 操作灵活、安全性高
    通过mmap内存映射文件,提供了一段可供随时写入的内存块,App只管往里面写数据,由操作系统负责将内存写到文件,
    不必担心crash导致数据丢失。
  • 空间占存少、数据量精简
    底层序列化/反序列化使用protobuf实现,以最少的数据量能展示最多的信息。
  • 性能高
    增量更新,避免每次进行,相对增量来说大数据量的全量写入。

出现的意义

MMKV的出现是为了替代SharedPreferences的轻量级存储解决方案。SharedPreferences需要被替换的原因主要存在以下问题:

  • 1.读写效率低
    主要原因是其本身的读写方式地址的:
  • 读写方式:I/O
  • 数据格式:xml
  • 写入方式:全量更新
    即每当需要更新一项数据,SharedPreferences的整个读写过程都是:
    所有数据转化为xml格式-->通过I/O方式写入
  • 2.容易导致ANR
    主要是由于同步提交(commit)、异步提交(apply)和获取数据getXXX()导致的。
//1.同步提交commit,commit提交是同步的,直到磁盘操作成功后才会完成
//所以当数据量比较大时,使用commit很容易引起ANR
public boolean commit(){
    MemoryCommitResult mcr =commitToMemory();
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr,null);
    try{
        mcr.writtenToDiaskLatch.await();
    }catch(InterruptedException e){
        reutrn false;
    }
}
//2.异步提交apply
//当数据量比较大时,使用apply也可能引起ANR
public void apply(){
    final long startTime =System.currentTimeMillis();
    
    final MemoryCommitResult mcr =commitToMemory();
    final Runable awaitCommit =new Runable(){
        @Override
        public void run(){
            //启用等待
            mcr.writtenToDiaskLatch.await();
            ...
        }
    };
    //将awaitCommit 添加到队列QueueWork中
    QueueWork.addFinisher(awaitCommit);
    
    Runable postWriteRunable = new Runable(){
        @Override
        public void run(){
            awaitCommit.run();
            QueueWork.removeFinisher(awaitCommit);
        }
    };
    //将写入任务加入到队列中,而写入任务在一个线程中执行
    SharedPreferencesImpl.this.enqueueDiskWrite(mcr,postWriteRunable);
    
    //为了保证异步任务及时完成,当生命周期处于handleStopService()、handlePauseActivity()、handleStopActivity()时会调用QueueWork.waitToFinish() 会等待写入任务执行完毕
    //waitToFinish():会一直等待写入任务执行完毕,其他什么都不做
    //当有很多写入任务,会依次执行;当文件很大时,效率很低,则容易造成ANR
    public static void waitToFinish(){
        Runable toFinish;
        while((toFinish=sPendingWorkFinishers.poll())!=null){
            toFinish.run();
        }
    }


//3. 获取数据getXXX()
//所有getXXX()方法都是同步的,在主线程调用get方法,必须等待SP加载完毕,也有可能导致ANR
//使用getSharedPreferences()最终会调用SharedPreferencesImpl#startLoadFormDish()开启一个线程异步读取数据
new Thread("SharedPreferencesImpl-load"){
    @Override
    public void run(){
        loadFromDisk();
    }
}.start();

//当我们正在读取一个比较大的数据,还没读取完,接着调用getXXX().
public String getString(String key,@Nullable String defValue){
    synchronized(mLock){
        awaitLoadedLocked();
        String v =(String)mMap.get(key);
        return v !=null ? v :defValue;
    }
}
//在同步方法内调用了wait(),会一直等待getSharedPreferences()开启线程读取完数据才能继续往下执行
//如果读取一个大的文件,也很大可能会造成ANR
    private void awaitLoadedLocked(){
    while(!mLoaded){
        mLock.wait();
    }catch(InterruptedException unused){}
    
    }
}

MMKV原理

  1. 读写方式:内存映射MMAP
    MMKV基于内存映射MMAP,下面主要介绍内存映射相关的内容:

1.1 定义

Linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,以初始化这个虚拟内存的内容,这个过程称为内存映射(memory mapping)


image.png

1.2 读写原理

  • 对文件进行mmap后,会在进程的虚拟内存分配地址空间&创建映射关系
  • 实现映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上。

1.3 优势

  • 减少数据拷贝次数
    对文件的读写操作只需从磁盘到用户主存的一次数据拷贝过程;
  • 操作数据深度快
    使用逻辑内存对磁盘文件进行映射,操作内存就相当于操作文件,不需要开启线程,和操作内存的速度一样快;MMAP提供一段可供随时写入的内存块,App只管往里面写数据,由操作系统如内存不足、进程退出等时间负责将内存回写到文件,不必担心crash导致数据丢失
  • 操作灵活、安全性高
    通过mmap内存映射文件,提供了一段可供随时写入的内存块,App只管往里面写数据,由操作系统负责将内存回写到文件,不必担心crash导致数据丢失。

2.数据存储方式:Protobuf
MMKV的序列化/反序列化使用protobuf实现,其采用了以T-V方式对数据进行二进制数据流存储,空间占存少、数据量精简,能以最少的数据量能表示最多的信息。


image.png
  1. 写入方式
    因为序列化/反序列化使用protobuf实现,在更新数据的时候,只需将数据追加在前数据后,效率更高,可实现增量更新
    本文来自Carson_Ho,其公众号:Carson带你学习Android
    查看原文

相关文章

网友评论

    本文标题:【学习】MMKV:微信团队开源的轻量级存储方案

    本文链接:https://www.haomeiwen.com/subject/jppqjltx.html