MMKV组件实现原理以及和SharedPreferences的比较(一)

  • Post author:
  • Post category:其他




MMKV组件简介

  • MMKV项目

    地址
  • MMKV是基于mmap内存映射关系的key-value组件,底层序列化/反序列化使用protobuf实现。性能高,稳定性强。从2015年就在微信上使用,已经移植到了Android/MacOS/Windows平台



SharedPreferences实现原理

  • SharedPreferences是Android提供的一种使用XML文件保存内容的机制,内部通过XML写入文件
  • 首先从熟悉的SharedPreferences入手

在这里插入图片描述

  • 特点:

    • 读写方式:直接I/O
    • 数据格式:xml
    • 写入方式:全量更新

  • SharedPreferencesImpl源码分析:
SharedPreferencesImpl(File file, int mode) {
    mFile = file;
    mBackupFile = makeBackupFile(file);
    mMode = mode;
    mLoaded = false;
    mMap = null;
    mThrowable = null;
    startLoadFromDisk();
}


private void startLoadFromDisk() {
    synchronized (mLock) {
        mLoaded = false;
    }
    new Thread("SharedPreferencesImpl-load") {
        public void run() {
            loadFromDisk();
        }
    }.start();
}


private void loadFromDisk() {
    synchronized (mLock) {
        if (mLoaded) {
            return;
        }
        if (mBackupFile.exists()) {
            mFile.delete();
            mBackupFile.renameTo(mFile);
        }
    }


    // Debugging
    if (mFile.exists() && !mFile.canRead()) {
        Log.w(TAG, "Attempt to read preferences file " + mFile + " without permission");
    }


    Map<String, Object> map = null;
    StructStat stat = null;
    Throwable thrown = null;
    try {
        stat = Os.stat(mFile.getPath());
        if (mFile.canRead()) {
            BufferedInputStream str = null;
            try {
                str = new BufferedInputStream(
                        new FileInputStream(mFile), 16 * 1024);
                map = (Map<String, Object>) XmlUtils.readMapXml(str);
            } catch (Exception e) {
                Log.w(TAG, "Cannot read " + mFile.getAbsolutePath(), e);
            } finally {
                IoUtils.closeQuietly(str);
            }
        }
    } catch (ErrnoException e) {
        // An errno exception means the stat failed. Treat as empty/non-existing by
        // ignoring.
    } catch (Throwable t) {
        thrown = t;
    }


    synchronized (mLock) {
        mLoaded = true;
        mThrowable = thrown;


        // It's important that we always signal waiters, even if we'll make
        // them fail with an exception. The try-finally is pretty wide, but
        // better safe than sorry.
        try {
            if (thrown == null) {
                if (map != null) {
                    mMap = map;
                    mStatTimestamp = stat.st_mtim;
                    mStatSize = stat.st_size;
                } else {
                    mMap = new HashMap<>();
                }
            }
            // In case of a thrown exception, we retain the old map. That allows
            // any open editors to commit and store updates.
        } catch (Throwable t) {
            mThrowable = t;
        } finally {
            mLock.notifyAll();
        }
    }
}

  • 在构造函数中利用startLoadFromDisk()->loadFromDiskLocked()将XML文件读取出来,读取出整个xml文件放入到HashMap当中
  • 可见它是将一整个xml文件当作操作对象,所以局部更新的时候需要对整个数据进行序列化进行更新



I/O

  • SharedPreferences使用的是直接I/O,虚拟内存被操作系统划分成两块:用户空间和内核空间,用户空间是用户程序代码执行的空间,内核空间是内核代码运行的空间,为了操作系统的安全,必须做好隔离,即使用户程序崩溃也不会影响系统内核

    在这里插入图片描述

  • 写入文件的流程

    • 调用write,通知内核需要写入数据的开始地址与长度
    • 内核将数据拷贝到内核缓存
    • 由操作系统调用,将数据拷贝到磁盘,完成写入

    • 经过了两次拷贝
  • I/O是一个耗时的操作,一般在进行I/O操作的时候放到子线程中执行,线程的调度是抢占式的,系统会为线程分配一个时间片 ,在这个事件 内执行线程任务,时间到了就要进行上下文切换,这时需要保存程序计数器和CPU寄存器(保存状态),用于下次执行可以加载此状态



MMAP

  • 上面提到的MMAP是linux中的映射关系,linux通过将一个虚拟内存区域与一个磁盘上的对象关联起来,来初始化整个虚拟内存的内容——内存映射

  • 实现这样的内存映射后,就可以采用指针的方式读写操作这一段内存,系统会自动写回到对应的文件磁盘上

  • 进程虚拟地址

    在这里插入图片描述


  • 内存映射可以减少一次拷贝



MMAP的优势

  • MMAP对文件的读写操作只需要 从磁盘到用户主存的一次数据拷贝,减少了数据拷贝次数,提高了文件读写效率
  • MMAP使用逻辑内存对此盘文件进行映射,操作内存就相当于操作文件,不需要开启线程,操作MMAP的速度和操作内存一样快
  • MMAP提供一段可以随时写入的内存块,APP只管往里面写数据,在内存不足,进程退出的情况下由操作系统负责将数据写回到文件,不必担心Crash导致数据丢失



版权声明:本文为qq_39424143原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。