返回

Windows文件预分配:如何避免写入整个文件(详细指南)

windows

如何在 Windows 上预分配文件而不触发全文件写入

引言

在 Windows 上为磁盘文件预分配空间是应用程序开发中的一项常见任务。然而,传统方法存在一个重大问题:在文件结尾附近写入数据时,Windows 可能开始写入整个文件。这会导致巨大的 I/O 开销,特别是在处理大型文件时。本文将介绍一种改进的方法,该方法可以在 Windows 10/11 上预分配文件,而不会遇到此问题。

问题

使用 SetFileInformationByHandleSetFilePointerEx 及其后的 SetEndOfFile 函数进行传统预分配会导致以下问题:

  • 在文件接近结尾时写入数据时,Windows 会写入整个文件。
  • 这会占用大量磁盘 I/O 资源,减慢应用程序性能。
  • 对于大型文件(例如数 TB),写入时间可能会非常长。

解决方法

为了避免上述问题,我们可以使用 FSCTL_SET_ZERO_DATA 控制代码进行预分配。此方法涉及以下步骤:

1. 获取文件句柄

使用 CreateFile 函数打开文件的句柄。

2. 设置文件大小

使用 SetFilePointerExSetEndOfFile 函数将文件大小扩展到所需的大小。

3. 预分配文件

使用 DeviceIoControl 函数并指定 FSCTL_SET_ZERO_DATA 控制代码。

4. 关闭文件句柄

使用 CloseHandle 函数关闭文件的句柄。

示例代码

以下 C++ 代码演示了使用 FSCTL_SET_ZERO_DATA 控制代码进行预分配:

#include <windows.h>

int main() {
    HANDLE hFile = CreateFile(
        "C:\\path\\to\\file.dat",
        GENERIC_READ | GENERIC_WRITE,
        FILE_SHARE_READ,
        NULL,
        CREATE_NEW,
        FILE_ATTRIBUTE_NORMAL,
        NULL
    );

    if (hFile == INVALID_HANDLE_VALUE) {
        printf("Error creating file: %d\n", GetLastError());
        return 1;
    }

    LARGE_INTEGER fileSize;
    fileSize.QuadPart = 1024 * 1024 * 1024; // 1GB
    SetFilePointerEx(hFile, fileSize, NULL, FILE_BEGIN);
    SetEndOfFile(hFile);

    BOOL success = DeviceIoControl(
        hFile,
        FSCTL_SET_ZERO_DATA,
        NULL,
        0,
        NULL,
        0,
        NULL,
        NULL
    );

    if (!success) {
        printf("Error preallocating file: %d\n", GetLastError());
        return 1;
    }

    CloseHandle(hFile);

    return 0;
}

优势

使用 FSCTL_SET_ZERO_DATA 预分配文件具有以下优势:

  • 它不会导致 Windows 在文件结尾附近写入时写入整个文件。
  • 即使文件接近驱动器容量,它也不会显著影响性能。
  • 它类似于 Linux 中 fallocate 函数的行为。

结论

使用 FSCTL_SET_ZERO_DATA 控制代码,可以在 Windows 10/11 上预分配文件,而无需担心写入整个文件的问题。这对于需要为大型文件分配磁盘空间的应用程序非常有用,因为它避免了性能瓶颈和不必要的 I/O 开销。

常见问题解答

1. 什么是预分配文件?

预分配文件是一种为文件分配磁盘空间的过程,使其可以被应用程序使用。它可以提高读写性能,防止文件碎片化。

2. 为什么传统预分配方法会导致写入整个文件?

Windows 使用一个称为 "写时复制" 的机制,当在文件的结尾附近写入数据时,它需要将整个文件写入磁盘。

3. 使用 FSCTL_SET_ZERO_DATA 的优点是什么?

使用 FSCTL_SET_ZERO_DATA 可以避免写入整个文件,因为它使用不同的机制来预分配磁盘空间。

4. 如何在代码中使用 FSCTL_SET_ZERO_DATA

使用 DeviceIoControl 函数并指定 FSCTL_SET_ZERO_DATA 控制代码。

5. 这个方法在所有 Windows 版本中都适用吗?

此方法适用于 Windows 10/11(NTFS)。