Windows文件预分配:如何避免写入整个文件(详细指南)
2024-03-03 19:48:31
如何在 Windows 上预分配文件而不触发全文件写入
引言
在 Windows 上为磁盘文件预分配空间是应用程序开发中的一项常见任务。然而,传统方法存在一个重大问题:在文件结尾附近写入数据时,Windows 可能开始写入整个文件。这会导致巨大的 I/O 开销,特别是在处理大型文件时。本文将介绍一种改进的方法,该方法可以在 Windows 10/11 上预分配文件,而不会遇到此问题。
问题
使用 SetFileInformationByHandle
和 SetFilePointerEx
及其后的 SetEndOfFile
函数进行传统预分配会导致以下问题:
- 在文件接近结尾时写入数据时,Windows 会写入整个文件。
- 这会占用大量磁盘 I/O 资源,减慢应用程序性能。
- 对于大型文件(例如数 TB),写入时间可能会非常长。
解决方法
为了避免上述问题,我们可以使用 FSCTL_SET_ZERO_DATA
控制代码进行预分配。此方法涉及以下步骤:
1. 获取文件句柄
使用 CreateFile
函数打开文件的句柄。
2. 设置文件大小
使用 SetFilePointerEx
和 SetEndOfFile
函数将文件大小扩展到所需的大小。
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)。