You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

内存映射中稀疏文件的行为探究及技术问询

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.17 03:02:35