iOS 中的 MMap 内存映射技术详解

什么是 MMap

MMap(Memory Mapping)是一种内存映射技术,它允许将文件或其他对象映射到进程的地址空间。在 iOS 开发中,mmap 是一个强大的系统调用,能够将磁盘文件的内容直接映射到内存地址空间,使得对文件的读写操作可以像访问内存一样简单高效。

与传统的文件 I/O(通过 read()/write() 系统调用)不同,mmap 通过虚拟内存机制,让应用程序可以像访问数组一样访问文件内容,操作系统会自动处理磁盘和内存之间的数据传输。

核心特点

  • 零拷贝:数据不需要在用户空间和内核空间之间复制
  • 延迟加载:只有真正访问数据时才从磁盘加载(页面调度)
  • 共享内存:多个进程可以映射同一文件实现进程间通信
  • 高效访问:随机访问文件时性能更优

MMap 的工作原理

传统文件 I/O 流程

1
2
应用程序 -> read() -> 内核缓冲区 -> 用户空间缓冲区 -> 应用程序
磁盘 -> DMA -> 内核缓冲区

这个过程涉及多次数据拷贝:

  1. 磁盘数据通过 DMA 拷贝到内核缓冲区
  2. 内核缓冲区数据拷贝到用户空间缓冲区
  3. 应用程序从用户空间缓冲区读取数据

MMap 文件映射流程

1
应用程序 -> 直接访问虚拟内存 -> 页表映射 -> 物理内存/磁盘

使用 mmap 后:

  1. 文件被映射到进程的虚拟地址空间
  2. 访问映射区域时触发缺页中断
  3. 操作系统自动从磁盘加载数据页到物理内存
  4. 应用程序直接读写内存,无需额外拷贝

虚拟内存与页面调度

mmap 依赖于操作系统的虚拟内存机制:

  • 虚拟地址空间:应用程序看到的是连续的虚拟地址
  • 物理内存页:实际数据存储在物理内存页中
  • 页表映射:虚拟地址通过页表映射到物理地址
  • 按需加载:只有访问时才加载对应页面(Lazy Loading)

MMap 的优势与劣势

优势

1. 性能优势

  • 减少数据拷贝次数(零拷贝技术)
  • 随机访问效率高,无需频繁 seek
  • 大文件处理时节省内存(不需要一次性加载)

2. 编程便利性

  • 像操作数组一样操作文件
  • 无需手动管理缓冲区
  • 多进程可以共享内存映射

3. 系统优化

  • 操作系统自动管理页面换入换出
  • 利用文件系统的缓存机制
  • 支持写时复制(Copy-on-Write)

劣势

1. 内存压力

  • 映射大文件会占用虚拟地址空间
  • 32位系统地址空间有限
  • 可能触发频繁的页面换入换出

2. 使用限制

  • 不适合小文件(映射开销大于直接读写)
  • 顺序读取时可能不如流式读取高效
  • 映射失败时错误处理复杂

3. 并发问题

  • 多进程写入需要额外的同步机制
  • 文件大小变化时需要重新映射
  • 内存映射区域的错误可能导致崩溃

iOS 中 MMap 的应用场景

1. 大文件读取

处理大型日志文件、数据库文件时,使用 mmap 可以避免将整个文件加载到内存:

1
2
3
4
5
// 传统方式(不推荐大文件)
let data = try Data(contentsOf: fileURL) // 一次性加载全部内容

// mmap 方式(推荐)
let data = try Data(contentsOf: fileURL, options: .alwaysMapped)

2. 进程间通信(IPC)

通过共享内存映射实现高效的进程间通信:

  • App 和 Extension 之间共享数据
  • 多进程架构中的数据共享
  • 插件系统的数据交换

3. 数据持久化

许多高性能数据库使用 mmap 来实现数据持久化:

  • MMKV(微信开源的高性能键值存储)
  • LMDB(Lightning Memory-Mapped Database)
  • Realm(部分场景使用 mmap)

4. 图片解码优化

在图片加载和解码时,使用 mmap 可以减少内存峰值:

1
2
3
4
// 使用 mmap 映射图片文件
if let data = try? Data(contentsOf: imageURL, options: .mappedIfSafe) {
let image = UIImage(data: data)
}

5. 日志系统

高性能日志库通常使用 mmap 来实现异步写入:

  • 减少日志写入对主线程的影响
  • 崩溃时日志不丢失
  • 支持大量日志快速写入

MMap 的基本使用

C 语言 API

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include <sys/mman.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

// 打开文件
int fd = open("/path/to/file", O_RDWR);
if (fd == -1) {
perror("open");
return;
}

// 获取文件大小
struct stat sb;
if (fstat(fd, &sb) == -1) {
perror("fstat");
close(fd);
return;
}

// 内存映射
void *addr = mmap(NULL, // 让系统选择映射地址
sb.st_size, // 映射大小
PROT_READ | PROT_WRITE, // 读写权限
MAP_SHARED, // 共享映射
fd, // 文件描述符
0); // 文件偏移量

if (addr == MAP_FAILED) {
perror("mmap");
close(fd);
return;
}

// 使用映射区域
char *data = (char *)addr;
data[0] = 'H';
data[1] = 'e';
data[2] = 'l';
data[3] = 'l';
data[4] = 'o';

// 同步到磁盘(可选)
msync(addr, sb.st_size, MS_SYNC);

// 解除映射
munmap(addr, sb.st_size);

// 关闭文件
close(fd);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
#import <Foundation/Foundation.h>
#import <sys/mman.h>
#import <sys/stat.h>
#import <fcntl.h>

@interface MMapHelper : NSObject

+ (NSData *)mapFileAtPath:(NSString *)path error:(NSError **)error;

@end

@implementation MMapHelper

+ (NSData *)mapFileAtPath:(NSString *)path error:(NSError **)error {
const char *filePath = [path UTF8String];

// 打开文件
int fd = open(filePath, O_RDONLY);
if (fd == -1) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
return nil;
}

// 获取文件大小
struct stat sb;
if (fstat(fd, &sb) == -1) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
close(fd);
return nil;
}

// 内存映射
void *addr = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd); // 映射后可以关闭文件描述符

if (addr == MAP_FAILED) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
return nil;
}

// 包装为 NSData
return [NSData dataWithBytesNoCopy:addr
length:sb.st_size
freeWhenDone:YES];
}

@end

// 使用示例
NSError *error;
NSData *data = [MMapHelper mapFileAtPath:@"/path/to/file" error:&error];
if (data) {
// 直接访问数据
const char *bytes = data.bytes;
NSLog(@"First byte: %c", bytes[0]);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
import Foundation

class MMapHelper {

/// 使用 mmap 映射文件
static func mapFile(at url: URL) throws -> Data {
let fd = open(url.path, O_RDONLY)
guard fd >= 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
defer { close(fd) }

// 获取文件大小
var sb = stat()
guard fstat(fd, &sb) == 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

let size = Int(sb.st_size)

// 内存映射
let addr = mmap(nil, size, PROT_READ, MAP_PRIVATE, fd, 0)
guard addr != MAP_FAILED else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

// 包装为 Data(自动管理内存)
return Data(bytesNoCopy: addr!, count: size, deallocator: .custom { ptr, length in
munmap(ptr, length)
})
}

/// 使用系统 API(推荐)
static func mapFileUsingFoundation(at url: URL) throws -> Data {
return try Data(contentsOf: url, options: .alwaysMapped)
}
}

// 使用示例
do {
let fileURL = URL(fileURLWithPath: "/path/to/file")
let data = try MMapHelper.mapFile(at: fileURL)

// 访问数据
data.withUnsafeBytes { (ptr: UnsafeRawBufferPointer) in
if let baseAddress = ptr.baseAddress {
let firstByte = baseAddress.load(as: UInt8.self)
print("First byte: \(firstByte)")
}
}
} catch {
print("Error: \(error)")
}

高级用法:共享内存

使用 mmap 实现进程间通信(IPC):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
import Foundation

class SharedMemory {
private let name: String
private var fileDescriptor: Int32 = -1
private var mappedAddress: UnsafeMutableRawPointer?
private let size: Int

init(name: String, size: Int) {
self.name = name
self.size = size
}

/// 创建共享内存
func create() throws {
// 创建共享内存对象
fileDescriptor = shm_open(name, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR)
guard fileDescriptor >= 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

// 设置共享内存大小
guard ftruncate(fileDescriptor, off_t(size)) == 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

// 映射到进程地址空间
mappedAddress = mmap(nil, size, PROT_READ | PROT_WRITE, MAP_SHARED, fileDescriptor, 0)
guard mappedAddress != MAP_FAILED else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
}

/// 打开已存在的共享内存
func open() throws {
fileDescriptor = shm_open(name, O_RDWR, 0)
guard fileDescriptor >= 0 else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

mappedAddress = mmap(nil, size, PROT_READ | PROT_WRITE, MAP_SHARED, fileDescriptor, 0)
guard mappedAddress != MAP_FAILED else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}
}

/// 写入数据
func write(data: Data) {
guard let address = mappedAddress else { return }
data.withUnsafeBytes { ptr in
memcpy(address, ptr.baseAddress, min(data.count, size))
}
}

/// 读取数据
func read(length: Int) -> Data? {
guard let address = mappedAddress else { return nil }
return Data(bytes: address, count: min(length, size))
}

/// 清理资源
func close() {
if let address = mappedAddress {
munmap(address, size)
mappedAddress = nil
}

if fileDescriptor >= 0 {
Darwin.close(fileDescriptor)
fileDescriptor = -1
}
}

/// 删除共享内存
func unlink() {
shm_unlink(name)
}

deinit {
close()
}
}

// 进程 A:创建并写入
let sharedMem = SharedMemory(name: "/my_shared_memory", size: 1024)
try? sharedMem.create()
let message = "Hello from Process A".data(using: .utf8)!
sharedMem.write(data: message)

// 进程 B:读取
let sharedMem2 = SharedMemory(name: "/my_shared_memory", size: 1024)
try? sharedMem2.open()
if let data = sharedMem2.read(length: 1024),
let message = String(data: data, encoding: .utf8) {
print("Received: \(message)")
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
#import <Foundation/Foundation.h>
#import <sys/mman.h>
#import <fcntl.h>

@interface SharedMemory : NSObject

@property (nonatomic, readonly) NSString *name;
@property (nonatomic, readonly) size_t size;

- (instancetype)initWithName:(NSString *)name size:(size_t)size;
- (BOOL)create:(NSError **)error;
- (BOOL)open:(NSError **)error;
- (void)writeData:(NSData *)data;
- (NSData *)readDataWithLength:(size_t)length;
- (void)close;
- (void)unlink;

@end

@implementation SharedMemory {
int _fd;
void *_addr;
}

- (instancetype)initWithName:(NSString *)name size:(size_t)size {
if (self = [super init]) {
_name = name;
_size = size;
_fd = -1;
_addr = NULL;
}
return self;
}

- (BOOL)create:(NSError **)error {
_fd = shm_open([_name UTF8String], O_CREAT | O_RDWR, S_IRUSR | S_IWUSR);
if (_fd < 0) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
return NO;
}

if (ftruncate(_fd, _size) != 0) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
close(_fd);
return NO;
}

_addr = mmap(NULL, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if (_addr == MAP_FAILED) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
close(_fd);
return NO;
}

return YES;
}

- (BOOL)open:(NSError **)error {
_fd = shm_open([_name UTF8String], O_RDWR, 0);
if (_fd < 0) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
return NO;
}

_addr = mmap(NULL, _size, PROT_READ | PROT_WRITE, MAP_SHARED, _fd, 0);
if (_addr == MAP_FAILED) {
if (error) {
*error = [NSError errorWithDomain:NSPOSIXErrorDomain
code:errno
userInfo:nil];
}
close(_fd);
return NO;
}

return YES;
}

- (void)writeData:(NSData *)data {
if (_addr) {
memcpy(_addr, data.bytes, MIN(data.length, _size));
}
}

- (NSData *)readDataWithLength:(size_t)length {
if (_addr) {
return [NSData dataWithBytes:_addr length:MIN(length, _size)];
}
return nil;
}

- (void)close {
if (_addr) {
munmap(_addr, _size);
_addr = NULL;
}
if (_fd >= 0) {
close(_fd);
_fd = -1;
}
}

- (void)unlink {
shm_unlink([_name UTF8String]);
}

- (void)dealloc {
[self close];
}

@end

实战案例:高性能日志系统

使用 mmap 实现一个简单的日志系统:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import Foundation

class MMapLogger {
private let fileURL: URL
private var fileHandle: FileHandle?
private var mappedData: UnsafeMutableRawPointer?
private var mappedSize: Int = 0
private var writeOffset: Int = 0
private let maxFileSize: Int
private let queue = DispatchQueue(label: "com.logger.mmap")

init(logPath: String, maxSize: Int = 10 * 1024 * 1024) { // 默认 10MB
self.fileURL = URL(fileURLWithPath: logPath)
self.maxFileSize = maxSize
setupLogFile()
}

private func setupLogFile() {
// 创建文件(如果不存在)
if !FileManager.default.fileExists(atPath: fileURL.path) {
FileManager.default.createFile(atPath: fileURL.path, contents: nil)
}

do {
// 打开文件
fileHandle = try FileHandle(forUpdating: fileURL)

// 扩展文件到指定大小
try fileHandle?.truncate(atOffset: UInt64(maxFileSize))

// 映射文件
let fd = fileHandle!.fileDescriptor
mappedData = mmap(nil, maxFileSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)

if mappedData == MAP_FAILED {
print("mmap failed")
mappedData = nil
} else {
mappedSize = maxFileSize
}
} catch {
print("Setup log file error: \(error)")
}
}

func log(_ message: String) {
queue.async { [weak self] in
guard let self = self,
let data = mappedData else { return }

let timestamp = Date().timeIntervalSince1970
let logEntry = "[\(timestamp)] \(message)\n"

guard let logData = logEntry.data(using: .utf8) else { return }

// 检查是否需要循环覆盖
if writeOffset + logData.count > mappedSize {
writeOffset = 0 // 循环写入
}

// 写入日志
logData.withUnsafeBytes { ptr in
memcpy(data.advanced(by: writeOffset), ptr.baseAddress, logData.count)
}

writeOffset += logData.count

// 同步到磁盘(可选,影响性能)
// msync(data, mappedSize, MS_ASYNC)
}
}

func flush() {
if let data = mappedData {
msync(data, mappedSize, MS_SYNC)
}
}

deinit {
if let data = mappedData {
munmap(data, mappedSize)
}
try? fileHandle?.close()
}
}

// 使用示例
let logger = MMapLogger(logPath: "/tmp/app.log")
logger.log("Application started")
logger.log("User logged in")
logger.log("Data synchronized")
logger.flush() // 确保写入磁盘

性能对比测试

对比传统文件 I/O 和 mmap 的性能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
import Foundation

class PerformanceTest {

static func testTraditionalIO(fileURL: URL, iterations: Int) -> TimeInterval {
let start = Date()

for i in 0..<iterations {
let data = "Log entry \(i)\n".data(using: .utf8)!
try? data.append(to: fileURL)
}

return Date().timeIntervalSince(start)
}

static func testMMap(fileURL: URL, iterations: Int) -> TimeInterval {
let start = Date()
let logger = MMapLogger(logPath: fileURL.path)

for i in 0..<iterations {
logger.log("Log entry \(i)")
}

logger.flush()
return Date().timeIntervalSince(start)
}
}

// 运行测试
let traditionalTime = PerformanceTest.testTraditionalIO(
fileURL: URL(fileURLWithPath: "/tmp/traditional.log"),
iterations: 10000
)

let mmapTime = PerformanceTest.testMMap(
fileURL: URL(fileURLWithPath: "/tmp/mmap.log"),
iterations: 10000
)

print("Traditional I/O: \(traditionalTime)s")
print("MMap: \(mmapTime)s")
print("MMap is \(traditionalTime / mmapTime)x faster")

注意事项与最佳实践

1. 文件大小变化

当文件大小需要改变时,必须重新映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func resizeMapping(newSize: Int) throws {
// 解除旧映射
if let oldAddr = mappedData {
munmap(oldAddr, mappedSize)
}

// 调整文件大小
try fileHandle?.truncate(atOffset: UInt64(newSize))

// 重新映射
let fd = fileHandle!.fileDescriptor
mappedData = mmap(nil, newSize, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)

guard mappedData != MAP_FAILED else {
throw NSError(domain: NSPOSIXErrorDomain, code: Int(errno))
}

mappedSize = newSize
}

2. 错误处理

访问映射区域时可能产生信号(如 SIGSEGV、SIGBUS):

1
2
3
4
// 设置信号处理器(Objective-C 中更常见)
signal(SIGBUS) { signal in
print("Bus error: attempting to access memory outside mapped region")
}

3. 内存建议(madvise)

告诉操作系统如何使用映射内存:

1
2
3
4
5
6
7
8
9
10
11
// 顺序访问
madvise(mappedData, mappedSize, MADV_SEQUENTIAL)

// 随机访问
madvise(mappedData, mappedSize, MADV_RANDOM)

// 不再需要
madvise(mappedData, mappedSize, MADV_DONTNEED)

// 预加载
madvise(mappedData, mappedSize, MADV_WILLNEED)

4. 线程安全

多线程访问映射区域时需要同步:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class ThreadSafeMMap {
private let lock = NSLock()
private var mappedData: UnsafeMutableRawPointer?

func write(data: Data, at offset: Int) {
lock.lock()
defer { lock.unlock() }

guard let addr = mappedData else { return }
data.withUnsafeBytes { ptr in
memcpy(addr.advanced(by: offset), ptr.baseAddress, data.count)
}
}
}

5. 内存压力处理

在内存紧张时释放映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MMapCache {
private var mappings: [String: UnsafeMutableRawPointer] = [:]

init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(handleMemoryWarning),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil
)
}

@objc private func handleMemoryWarning() {
// 释放部分映射
mappings.forEach { _, addr in
munmap(addr, mappedSize)
}
mappings.removeAll()
}
}

MMap 与其他技术对比

MMap vs 传统文件 I/O

特性 MMap 传统 I/O
数据拷贝 无需用户态内核态拷贝 需要多次拷贝
随机访问 高效 需要 seek
顺序访问 较好 很好
小文件 映射开销大 高效
大文件 节省内存 需要大量内存
编程复杂度 简单(像数组) 需要缓冲区管理

MMap vs NSCache

特性 MMap NSCache
持久化 自动持久化 仅内存
容量限制 文件大小 内存大小
崩溃恢复 数据不丢失 数据丢失
访问速度 略慢(可能缺页) 很快
进程共享 支持 不支持

MMap vs Core Data

特性 MMap Core Data
查询能力 强大的查询
关系管理 支持关系
原始性能 更快 稍慢
学习曲线
适用场景 简单数据/日志 复杂数据模型

实际应用:MMKV 简介

MMKV 是微信开源的基于 mmap 的高性能键值存储框架:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import MMKV

// 初始化
MMKV.initialize(rootDir: nil)

// 获取默认实例
let mmkv = MMKV.default()

// 写入数据
mmkv?.set("value", forKey: "key")
mmkv?.set(123, forKey: "number")
mmkv?.set(true, forKey: "bool")

// 读取数据
let value = mmkv?.string(forKey: "key")
let number = mmkv?.int32(forKey: "number")
let bool = mmkv?.bool(forKey: "bool")

// MMKV 的优势
// 1. 基于 mmap,性能极高
// 2. 进程安全(多进程访问)
// 3. 数据加密支持
// 4. 增量更新,不会阻塞
// 5. 自动处理文件损坏

总结

MMap 是一项强大的技术,在以下场景中特别有用:

  1. 大文件处理:避免一次性加载整个文件到内存
  2. 高性能日志:异步写入,崩溃不丢失
  3. 进程间通信:共享内存方式实现 IPC
  4. 数据库实现:LMDB、MMKV 等都基于 mmap
  5. 图片解码:减少内存峰值

但也需要注意:

  • 小文件不适合使用 mmap(映射开销大)
  • 需要处理好内存映射的生命周期
  • 多进程写入需要额外的同步机制
  • 文件大小变化时需要重新映射

掌握 mmap 技术,可以显著提升 iOS 应用在文件操作、数据持久化和进程间通信等方面的性能。