
MMKV是什么
MMKV 是基于 mmap (memory mapping)内存映射的 key-value 组件,底层序列化/反序列化使用 protobuf 实现,性能高,稳定性强。
简单来说,它就是一个【可以用于替代SP】,操作与SP类似的存储组件。
内存映射的定义
内存映射(Memory Mapping)是一种将文件内容映射到进程的虚拟地址空间的技术。在这种机制下,文件可以被视为内存的一部分,从而允许程序直接对这部分内存进行读写操作,而无需传统的文件 I/O 调用。这种方法不仅简化了文件操作,还提高了处理效率。
mmap 系统调用概述
mmap 是实现内存映射的关键系统调用。它创建了文件内容和进程地址空间之间的直接映射,使得文件的一部分或全部可以直接映射到进程的地址空间中。这样,文件的读写就变得像内存访问一样高效。
mmap 系统调用在 Linux 和类 Unix 系统中提供了内存映射的功能。它允许程序员将整个文件或文件的一部分映射到进程的地址空间。通过这种方式,文件内容可以通过指针直接访问,就像访问普通的内存数组一样,这极大地提高了文件操作的效率和直观性。
1 |
|
在这段代码中,打开了一个文件并使用 mmap 将其映射到内存。直接通过返回的指针来访问文件内容,而不需要进行传统的文件读写操作。
SharedPreference缺陷
读写效率低
主要原因是其本身的读写方式导致的:
读写方式:I/O
数据格式:xml
写入方式:全量更新
即每当需要更新一项数据,SharedPreferences的整个读写过程都是:将所有数据转化成xml格式 -> 通过I/O方式写入。
写文件流程:
1、调用write向内核发起系统调用,上下文从用户态切换为内核态;
2、CPU 将用户缓冲区中的数据拷贝到内核空间的缓冲区,(CPU拷贝);
3、CPU 利用 DMA控制器将数据从内核缓冲区拷贝到磁盘缓冲区进行数据传输。(DMA拷贝);
4、上下文从内核态切换回用户态,write 系统调用执行返回
容易导致ANR
主要是由于同步提交(commit)、异步提交(Apply) 和 获取数据getXX()导致的。
1 | /* |
MMKV性能优势
MMAP对文件的读写操作只需要从磁盘到用户主存的一次数据拷贝过程,减少了数据的拷贝次数,提高了文件操作效率。
MMAP使用逻辑内存对磁盘文件进行映射,操作内存就相当于操作文件,不需要开启线程,操作MMAP的速度和操作内存的速度一样快;
MMAP提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统如内存不足、进程退出等时候负责将内存回写到文件。
支持的数据类型:
- 支持以下 Java 语言基础类型:
- boolean、int、long、float、double、byte[]
- 支持以下 Java 类和容器:
- String、Set
- 任何实现了Parcelable的类型
数据组织
数据序列化方面选用 protobuf 协议,pb 在性能和空间占用上都有不错的表现。写入优化
考虑到主要使用场景是频繁地进行写入更新,我们需要有增量更新的能力。我们考虑将增量 kv 对象序列化后,append 到内存末尾。空间增长
使用 append 实现增量更新带来了一个新的问题,就是不断 append 的话,文件大小会增长得不可控。我们需要在性能和空间上做个折中。
无法处理输入事件。常见的原因包括主线程中进行了耗时的I/O操作、复杂的计算、长时间的等待等。
MMKV的使用
初始化以及修改根目录
1 | MMKV.initialize(this)//(与下面的几选一,一般就使用这个就行) |
获取MMKV实例
1 | import com.tencent.mmkv.MMKV; |
存取数据
1 | /** 添加/更新数据 **/ |
MMKV原理
对文件进行mmap,会在进程的虚拟内存分配地址空间创建映射关系。
实现这样的映射关系后,就可以采用指针的方式读写操作这一段内存,而系统会自动回写到对应的文件磁盘上
少了把数据传给内核,再由内核存到文件的步骤,不需要内核作为中转,可以直接在用户控件对文件进行读写,少了一次CPU的拷贝
通过 mmap 内存映射文件,提供一段可供随时写入的内存块,App 只管往里面写数据,由操作系统负责将内存回写到文件,不必担心 crash 导致数据丢失。
mmap 是一种在 Linux 和其他类 Unix 操作系统中使用的系统调用,它允许程序将一个文件或者设备映射到内存中。这样做的好处是可以直接通过内存操作来访问文件内容,而不需要使用传统的 read 和 write 系统调用。mmap 提供了一种高效的方式来处理文件数据,特别是在需要频繁读写大文件的场景下。
内存映射:mmap 将文件内容映射到进程的地址空间,使得文件内容看起来就像是内存中的一部分。
高效访问:由于文件内容被映射到内存,所以访问文件数据就像访问内存一样快。
共享内存:使用 mmap 可以创建共享内存区域,这对于进程间通信(IPC)非常有用。
自动同步:对映射区域的修改会自动同步回文件,无需显式调用 write 操作。
内存管理:mmap 可以帮助操作系统更好地管理内存,因为它允许操作系统在需要时将文件内容从物理内存中换出到磁盘。
文件锁定:mmap 可以用于文件锁定,防止其他进程修改文件。
支持匿名映射:除了文件映射外,mmap 还支持创建匿名映射,这种映射不与任何文件关联,通常用于动态内存分配。
mmap 系统调用和直接使用IPC共享内存之间的差异
mmap 系统调用用于将文件映射到进程的地址空间中,而共享内存是一种不同的机制,用于进程间通信。这两种方法都用于数据共享和高效的内存访问,但它们有一些关键区别:
1. 数据源和持久化:
mmap: 通过 mmap 映射的数据通常来自文件系统中的文件。这意味着数据是持久化的——即使程序终止,文件中的数据依然存在。当你通过映射的内存区域修改数据时,这些更改最终会反映到磁盘上的文件中。
共享内存:共享内存是一块匿名的(或者有时与特定文件关联的)内存区域,它可以被多个进程访问。与 mmap 映射的文件不同,共享内存通常是非持久的,即数据仅在计算机运行时存在,一旦系统关闭或重启,存储在共享内存中的数据就会丢失。
2. 使用场景:
- mmap:mmap 特别适合于需要频繁读写大文件的场景,因为它可以减少磁盘 I/O 操作的次数。它也允许文件的一部分被映射到内存中,这对于处理大型文件尤为有用。
- 共享内存:共享内存通常用于进程间通信(IPC),允许多个进程访问相同的内存区域,这样可以非常高效地在进程之间交换数据。
3. 性能和效率:
mmap:映射文件到内存可以提高文件访问的效率,尤其是对于随机访问或频繁读写的场景。系统可以利用虚拟内存管理和页面缓存机制来优化访问。
共享内存:共享内存提供了一种非常快速的数据交换方式,因为所有的通信都在内存中进行,没有文件 I/O 操作。
4. 同步和一致性:
mmap:使用 mmap 时,必须考虑到文件内容的同步问题。例如,使用 msync 调用来确保内存中的更改被同步到磁盘文件中。
共享内存:在共享内存的环境中,进程需要使用某种形式的同步机制(如信号量、互斥锁)来避免竞争条件和数据不一致。
mmap 和页缓存
页缓存涉及情况
当使用 mmap 映射文件到内存时,操作系统利用页缓存来优化对这些文件数据的访问。页缓存是操作系统的一部分,用于存储从磁盘读取的数据页。
访问 mmap 映射的文件时,并不是每次读取都会直接触及磁盘。如果所需数据已经在页缓存中(由于之前的读取操作),则直接从缓存中获取数据,而不需要磁盘 I/O。
物理内存页
- 无论是 mmap 映射的文件还是共享内存,最终都是以物理内存页的形式存在。操作系统通过管理这些内存页来控制程序的内存访问。
共享内存和内存页
1. 共享内存也在内存页中:
- 共享内存确实也是在物理内存页中分配的。当我们谈论共享内存没有“缓存”,是指它不依赖于磁盘的页缓存,因为共享内存不是基于文件的。
2. 实时性和磁盘 I/O:
共享内存的实时性体现在它提供了一种直接访问物理内存的方式,而无需经过文件系统或磁盘 I/O。
对于共享内存,一旦数据被写入内存,它就立即对所有共享该内存区域的进程可见,没有额外的读取延迟。
总结
mmap 和共享内存都使用物理内存页。不同之处在于 mmap 通常用于映射文件到内存,因此它与磁盘的页缓存密切相关,可以减少对磁盘的访问。
共享内存不涉及文件系统或磁盘 I/O,因此提供了更快速、更直接的内存访问,这在需要极低通信延迟的应用中非常重要。
mmap 与文件 I/O
传统文件 I/O 的局限性
传统文件 I/O 操作,比如 read 和 write,虽然直观易懂,但它们有一定的局限性。每次读写操作都需要从用户空间切换到内核空间,这导致额外的上下文切换开销,特别是在频繁的小规模读写操作中,这种开销尤为明显。
效率比较
功能 | 传统文件I/O | mmap |
---|---|---|
数据访问 | 通过系统调用进行读写 | 直接内存访问 |
CPU开销 | 高(频繁的用户态/内核态切换) | 低(减少上下文切换) |
适用场景 | 小文件或不频繁访问的文件 | 大文件或频繁访问的文件 |
内存效率 | 一般 | 高(利用页面缓存) |