Megabonk免安装绿色版
165.8M · 2025-09-16
C++底层机制推荐阅读
【C++基础知识】深入剖析C和C++在内存分配上的区别
【底层机制】【C++】vector 为什么等到满了才扩容而不是提前扩容?
【底层机制】malloc 在实现时为什么要对大小内存采取不同策略?
【底层机制】剖析 brk 和 sbrk的底层原理
【底层机制】为什么栈的内存分配比堆快?
【底层机制】右值引用是什么?为什么要引入右值引用?
【底层机制】auto 关键字的底层实现机制
【底层机制】std::unordered_map 扩容机制
以下深入浅出地讲解稀疏文件(Sparse File)这个概念。这对于处理需要高效存储大型文件(尤其是那些有很多“空洞”的文件)的场景至关重要。
核心概念:稀疏文件是一种计算机文件存储技术,其中文件中的空数据块(通常是由一串零字节组成,称为“空洞”)不会实际分配物理磁盘空间。文件系统只是在元数据中记录这些空洞的位置和大小。
一个生动的比喻: 想象一本有1000页的书,但只有第1、第500和第1000页有文字,其他页都是完全空白的。
在文件系统中,那些“空白的页”就是空洞,它们不会占用实际的磁盘块。
操作系统和文件系统共同协作来支持这一特性(如NTFS, ext4, btrfs, APFS等都支持)。
元数据管理:文件系统不再使用简单的“块指针数组”来记录文件的所有块。对于稀疏文件,它使用一种更高效的数据结构(如ext4中的extent tree
)来记录两种区域:
读取时的处理:当应用程序请求读取文件的某个范围时:
写入时的处理:当应用程序试图写入数据时:
C++标准库本身没有直接提供创建或操作稀疏文件的特殊函数。操作依赖于平台特定的API。关键在于如何在创建文件时设置相应的属性。
fcntl.h
和 <unistd.h>
)在Linux上,稀疏文件是自动支持的。你不需要做任何特殊的事情来“启用”它。你只需要以一种可以创建空洞的方式来写入文件。
创建空洞的标准方法:使用 lseek
跳过一段距离,然后写入。
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
int main() {
int fd = open("sparse_file.bin", O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (fd == -1) {
// Error handling
return 1;
}
// 1. 在文件开头写入一些真实数据
const char* data = "Hello, Sparse World!";
write(fd, data, strlen(data));
// 2. 创建一个大空洞:向前寻址 1 GiB
off_t hole_size = 1LL * 1024 * 1024 * 1024; // 1 GiB
lseek(fd, hole_size, SEEK_CUR);
// 3. 在文件末尾(跳过1GiB后)再写入一些数据
const char* end_data = "This is at the end.";
write(fd, end_data, strlen(end_data));
close(fd);
return 0;
}
使用 ls -lh
查看,这个文件会显示为大约 1GB 的大小,但使用 du -h
查看,它占用的磁盘空间只有几KB。
显式打洞(更高效的方法):Linux 4.15+ 提供了 fallocate
系统调用,可以使用 FALLOC_FL_PUNCH_HOLE
模式来显式地“打洞”,释放文件中某部分已分配的块。这对于收缩文件或清除中间部分数据非常有用。
Windows.h
)Windows 需要显式地设置文件为稀疏属性。
#include <Windows.h>
#include <cstring>
int main() {
// 创建文件
HANDLE hFile = CreateFileW(
L"sparse_file.bin",
GENERIC_WRITE,
0,
NULL,
CREATE_ALWAYS,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
// Error handling
return 1;
}
// 1. 关键步骤:将文件标记为稀疏文件
DWORD bytesReturned;
DeviceIoControl(
hFile,
FSCTL_SET_SPARSE, // 控制代码:设置稀疏文件
NULL,
0,
NULL,
0,
&bytesReturned,
NULL
);
// 2. 写入初始数据
const char* data = "Hello, Sparse World!";
DWORD bytesWritten;
WriteFile(hFile, data, strlen(data), &bytesWritten, NULL);
// 3. 移动文件指针,创建空洞
LARGE_INTEGER distance;
distance.QuadPart = 1LL * 1024 * 1024 * 1024; // 移动 1 GiB
SetFilePointerEx(hFile, distance, NULL, FILE_CURRENT);
// 4. 在末尾写入数据
const char* end_data = "This is at the end.";
WriteFile(hFile, end_data, strlen(end_data), &bytesWritten, NULL);
CloseHandle(hFile);
return 0;
}
DeviceIoControl
可能会失败)。rsync
、tar
(带有 --sparse
选项)、Windows Backup
等,能够识别稀疏文件并在目的地正确地重建它们。ls -l
或 GetFileSize
报告的是文件“看起来”有多大,包括空洞。du
或 GetCompressedFileSize
报告的是文件实际占用了多少磁盘块。
务必区分这两者,避免混淆。特性 | 描述 |
---|---|
目的 | 高效存储含有大量零字节(空洞)的大型文件。 |
机制 | 文件系统元数据记录空洞的位置和大小,不分配物理存储空间。读取时动态返回零。 |
优势 | 节省磁盘空间、提高I/O效率、减少碎片。 |
C++实现 | Linux:自动支持,用lseek +write 创建空洞。Windows:需用FSCTL_SET_SPARSE 显式设置属性,再用SetFilePointerEx +WriteFile 。 |
注意事项 | 文件系统兼容性、备份工具的选择、逻辑大小与物理大小的区别。 |
希望这份详细的解释能帮助你全面理解并在C++项目中有效地利用稀疏文件。如果你有更多具体的使用场景或问题,我们可以继续深入探讨。
C++底层机制推荐阅读
【C++基础知识】深入剖析C和C++在内存分配上的区别
【底层机制】【C++】vector 为什么等到满了才扩容而不是提前扩容?
【底层机制】malloc 在实现时为什么要对大小内存采取不同策略?
【底层机制】剖析 brk 和 sbrk的底层原理
【底层机制】为什么栈的内存分配比堆快?
【底层机制】右值引用是什么?为什么要引入右值引用?
【底层机制】auto 关键字的底层实现机制
【底层机制】std::unordered_map 扩容机制