内存映射中稀疏文件的行为探究及技术问询
Windows稀疏文件内存映射写入的疑问解答
问题背景
研究稀疏文件的内存映射操作时发现:写入操作至少占用64KB空间;向逻辑地址下一个64KB区域写入时,文件大小增加64KB(总大小达128KB);即便仅在逻辑地址0和65536处各写入1字节,稀疏文件仍占用128KB空间。针对此现象,以下是具体问题的解答:
1. 这是稀疏文件的正确使用方式吗?
这是内存映射操作稀疏文件的正常表现,但并非最优的稀疏文件使用方式。
稀疏文件的核心是让未写入的区间不占用实际磁盘空间,但内存映射写入单个字节时,Windows会为该字节所在的**分配单元(NTFS簇或内存映射页块)**分配完整的磁盘空间,而非仅占用1字节。
如果要严格控制稀疏文件的空间占用,建议结合FSCTL_SET_ZERO_DATA将未使用的区间标记为稀疏,或直接使用WriteFile等文件写入API并配合稀疏文件相关IOCTL操作,而非依赖内存映射的自动分配逻辑。
2. 64KB的空间增长是Windows的系统设置吗?
是的,这和NTFS文件系统的簇大小直接相关。
当启用稀疏文件后,文件系统会以簇为单位分配实际磁盘空间。64KB通常是NTFS在大分区(如1TB以上)的默认簇大小,也可能是手动设置的分区簇大小。此外,内存映射的写入会触发虚拟内存页的提交,Windows可能会将多个内存页合并为一个文件簇分配,最终表现为64KB的空间增长。
你可以通过命令fsutil fsinfo ntfsinfo C:(替换为目标驱动器号)查看当前分区的簇大小,确认是否为64KB。
3. 先写入高逻辑地址再写入低地址,文件大小和占用空间如何?
- 文件的逻辑大小会显示为512KB(由写入的最高偏移位置决定);
- 实际磁盘占用为两个64KB块(共128KB),仅包含被写入的分配单元。
可以用fsutil sparse queryrange sparse_test.txt查看稀疏文件的已分配区间,用dir /s sparse_test.txt对比文件的逻辑大小和实际磁盘占用。
测试代码
#include <iostream> #include <windows.h> #include <fileapi.h> using namespace std; const char* FILENAME = "sparse_test.txt"; HANDLE hFile = nullptr; void writeSparse(BYTE* data, int n, int offset) { for (size_t i = 0; i < 1024 * n; i += offset) { data[i] = 1; cout << "Wrote byte " << i / 1024 + 1 << " at position " << i << "\n"; } } void writeSequential(BYTE* data, int n, int offset) { for (size_t i = 0; i < 1024 * n; i += offset) { memset(data + i, '2', offset); cout << "Wrote byte 2 at position " << offset * i << "-" << (i + 1) * offset << "\n"; } } int main(int argc, char* argv[]) { if (argc != 4) { cerr << "Usage: " << argv[0] << " <mode 1 or 2>\n"; return 1; } hFile = CreateFileA(FILENAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwTemp; BOOL bSparse = DeviceIoControl(hFile, FSCTL_SET_SPARSE, NULL, 0, NULL, 0, &dwTemp, NULL); HANDLE hMapping = CreateFileMappingA(hFile, NULL, PAGE_READWRITE, 0, 1024 * 1024, NULL); BYTE* data = (BYTE*)MapViewOfFile(hMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); if (data == NULL) { cerr << "Mapping failed: " << GetLastError() << "\n"; return 1; } (stoi(argv[1]) == 1) ? writeSparse(data, stoi(argv[2]), stoi(argv[3]) * 1024) : writeSequential(data, stoi(argv[2]), stoi(argv[3]) * 1024); UnmapViewOfFile(data); CloseHandle(hMapping); CloseHandle(hFile); return 0; }
代码使用说明
将代码编译为可执行文件(如test.exe),运行时需传入三个参数:
- 第一个参数为模式:1表示分散写入(在每个指定区间写入第一个字节),2表示连续写入(写入指定跳跃区间内的所有字节)。
- 第二个参数表示最大写入数据量,单位为千字节(KB)。
- 第三个参数为每次写入操作的偏移量,单位为千字节(KB)。
示例:
- 分散写入:
test.exe 1 128 4表示以4KB为跳跃间隔,在每个区间写入第一个字节,最大写入至128KB位置。 - 连续写入:
test.exe 2 128 4表示以4KB为跳跃间隔,写入每个区间内的所有字节。
内容的提问来源于stack exchange,提问作者game mone
相关产品推荐
相关产品推荐

