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导致数据丢失