单字节读取有很长的路要走,从磁板/闪存单元到您的本地 Java 变量。这是单个字节经过的路径:
- 磁板/闪速电池
- 内部硬盘缓冲区
- SATA/IDE 总线
- SATA/IDE 缓冲区
- PCI/PCI-X 总线
- 计算机的数据总线
- 计算机的 RAM 通过DMA
- 操作系统Page-cache
- Libc 读取缓冲区,也就是用户空间
fopen() 读取缓冲区
- 本地 Java 变量
出于性能原因,操作系统完成的大部分文件缓冲都保存在页面缓存中,将最近读取和写入的文件内容存储在 RAM 上。
这意味着 Java 代码的每次读写操作都是在本地缓冲区中完成的:
FileInputStream fis = new FileInputStream("/home/vz0/F.txt");
// This byte comes from the user space buffer.
int oneByte = fis.read();
一个页面通常是一个 4KB 的内存块。每个页面都有一些特殊的标志和属性,其中之一是“脏页”,这意味着该页面有一些未写入物理媒体的修改数据。
一段时间后,当操作系统决定将脏数据刷新回磁盘时,它会将数据发送到与原来相反的方向。
每当两个不同的进程将数据写入同一个文件时,产生的行为是:
- 不可能,如果文件被锁定。第二个进程将无法打开文件。
- 未定义,如果写入文件的同一区域。
- 预期,如果对文件的不同区域进行操作。
“区域”取决于应用程序使用的内部缓冲区大小。例如,在一个 2 MB 的文件上,两个不同的进程可能会写入:
- 前 1kB 数据中的一个 (0; 1024)。
- 另一个关于最后 1kB 的数据(2096128;2097152)
仅当本地缓冲区大小为 2 MB 时,才会发生缓冲区重叠和数据损坏。在 Java 上,您可以使用 Channel IO 来读取文件,并对里面发生的事情进行细粒度控制。
许多事务性数据库通过发出sync operation 来强制将本地 RAM 缓冲区中的一些写入写回磁盘。与单个文件相关的所有数据都被刷新回磁板或闪存单元,有效地确保在断电时不会丢失任何数据。
最后,memory mapped file 是一个内存区域,它允许用户进程直接从页面缓存读取和写入页面缓存,绕过用户空间缓冲。
页面缓存系统对于protected mode 多任务操作系统的性能至关重要,每个现代操作系统(Windows NT 及以上、Linux、MacOS、*BSD)都支持所有这些功能。