Android 开发中,咱们可能须要记录一些文件。例如记录 log 文件。如果应用流来写文件,频繁操作文件 io 可能会引起性能问题。
为了升高写文件的频率,咱们可能会采纳缓存肯定数量的 log,再一次性把它们写到文件中。如果 app 异样退出,咱们有可能会失落内存中的 log 信息。
那么有什么比拟稳当的写文件形式,既能升高 io,又能尽可能地保证数据被写入文件呢?
mmap 概念
mmap 是一种内存映射文件的办法(memory-mapped),行将一个文件或者其它对象映射到过程的地址空间,实现文件磁盘地址和过程虚拟地址空间中一段虚拟地址的一一对映关系。
特点:实现这样的映射关系后,过程就能够采纳指针的形式读写操作这一段内存,而零碎会主动回写脏页面到对应的文件磁盘上,即实现了对文件的操作而不用再调用 read,write 等零碎调用函数。相同,内核空间对这段区域的批改也间接反映用户空间,从而能够实现不同过程间的文件共享。如下图所示:
mmap 内存映射原理
mmap 内存映射的实现过程,总的来说能够分为三个阶段:
利用过程启动映射,在过程的虚拟地址空间中,寻找一段闲暇的满足要求的间断的虚拟地址作为映射区域;调用零碎函数 mmap,实现文件物理地址和过程虚拟地址的一一映射;利用过程对映射区域拜访,引发缺页异样,实现文件内容到物理内存(主存)的拷贝。
mmap 优缺点
- 只有一次数据拷贝:当产生缺页异样时,间接将数据从磁盘拷贝到过程的用户空间,跳过了页缓存。
- 实现了用户空间和内核空间的高效交互方式:两空间的各自批改操作能够间接反映在映射的区域内,从而被对方空间及时捕获。提供过程间共享内存及互相通信的形式。
不论是父子过程还是无亲缘关系的过程,都能够将本身用户空间映射到同一个文件或匿名映射到同一片区域。从而通过各自对映射区域的改变,达到过程间通信和过程间共享的目标。
同时,如果过程 A 和过程 B 都映射了区域 C,当 A 第一次读取 C 时通过缺页从磁盘复制文件页到内存中;但当 B 再读 C 的雷同页面时,尽管也会产生缺页异样,然而不再须要从磁盘中复制文件过去,而可间接应用曾经保留在内存中的文件数据。
mmap 留神点
对于大文件而言,内存映射比一般 IO 流要快,小文件则未必;不要常常调用 MappedByteBuffer.force() 办法,这个办法强制操作系统将内存中的内容写入硬盘,所以如果你在每次写内存映射文件后都调用 force() 办法,你就不能真正从内存映射文件中获益,而是跟 disk IO 差不多。
读写内存映射文件是操作系统来负责的,因而,即便你的 Java 程序在写入内存后就挂掉了,只有操作系统工作失常,数据就会写入磁盘。如果电源故障或者主机瘫痪,有可能内存映射文件还没有写入磁盘,意味着可能会失落一些要害数据。
Android 中的 Binder 也利用的 mmap。Binder 传递数据时,只须要复制一次,就能把数据传递到另一个过程中。
Android 中应用 mmap
Android 中应用 mmap,能够通过 RandomAccessFile 与 MappedByteBuffer 来配合。通过 randomAccessFile.getChannel().map
获取到 MappedByteBuffer
。而后调用 ByteBuffer 的 put 办法增加数据。
应用 RandomAccessFile 来获取 MappedByteBuffer
private static void writeDemo() {System.out.println("[writeDemo] start");
String inputStr = "This write demo. 维多利亚女王纪念碑是位于英国伦敦的一座大型纪念碑和雕塑组合,建于 1911 年。";
byte[] inputBytes = inputStr.getBytes();
final int length = inputBytes.length;
try {RandomAccessFile randomAccessFile = new RandomAccessFile("out/writeDemo.txt", "rw");
MappedByteBuffer mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, length);
mappedByteBuffer.put(inputBytes);
} catch (Exception e) {System.out.println(e.getMessage());
e.printStackTrace();}
System.out.println("[writeDemo] end");
}
private static void writeAppendDemo() {System.out.println("[writeAppendDemo] start");
String appendText = "\nThis is append text. 纪念碑的基座由 2300 吨汉白玉形成。";
byte[] bytes = appendText.getBytes();
final int length = bytes.length;
try {RandomAccessFile randomAccessFile = new RandomAccessFile("out/writeDemo.txt", "rw");
MappedByteBuffer mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, randomAccessFile.length(), length);
mappedByteBuffer.put(bytes);
} catch (Exception e) {System.out.println(e.getMessage());
e.printStackTrace();}
System.out.println("[writeAppendDemo] end");
}
Android 中应用 MappedByteBuffer 写入文件。记住以后写文件的地位 gCurrentLogPos,解决文件增长的问题。
File dir = new File(logFileDir);
if (!dir.exists()) {boolean mk = dir.mkdirs();
Log.d(defTag, "make dir" + mk);
}
File eFile = new File(logFileDir + File.separator + fileName);
byte[] strBytes = logContent.getBytes();
try {RandomAccessFile randomAccessFile = new RandomAccessFile(eFile, "rw");
MappedByteBuffer mappedByteBuffer;
final int inputLen = strBytes.length;
if (!eFile.exists()) {boolean nf = eFile.createNewFile();
Log.d(defTag, "new log file" + nf);
mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE);
} else {mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, inputLen);
}
if (mappedByteBuffer.remaining() < inputLen) {mappedByteBuffer = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_WRITE, gCurrentLogPos, LOG_FILE_GROW_SIZE + inputLen);
}
mappedByteBuffer.put(strBytes);
gCurrentLogPos += inputLen;
} catch (Exception e) {Log.e(defTag, "WriteRunnable run:", e);
if (!eFile.exists()) {boolean nf = eFile.createNewFile();
Log.d(defTag, "new log file" + nf);
}
FileOutputStream os = new FileOutputStream(eFile, true);
os.write(logContent.getBytes());
os.flush();
os.close();}
Android 零根底入门教程视频参考