如何通过编程强制Windows更新正在写入的文件的磁盘报告大小?
Windows下程序运行时日志文件大小在
dir中显示异常的编程解决方法 问题描述
我开发了一个程序,会在事件触发时打开ASCII日志文件并向末尾写入文本行。事件触发时间不确定,可能每秒多次,也可能数小时一次。这部分功能正常,但遇到了Windows系统下的一个问题:程序运行时,在DOS shell里输入dir命令查看文件列表时,日志文件大小始终显示为0字节,直到程序关闭文件。有意思的是,如果直接输入dir my_file_name.log指定文件名,文件大小会正常显示,但仅输入dir就不行。
这是Windows的系统“特性”,临时解决方法是在DOS窗口执行type my_file_name.log > nul强制更新文件大小显示,但我想在程序内部通过编程实现同样的效果,不用用户手动操作。
我试过两种方法都没成功:
- 模拟
type命令的行为,单独调用fopen()和fclose()打开再关闭日志文件,没用; - 先调用
fclose()关闭日志句柄,再用追加模式fopen()重新打开,但fopen()调用失败了(而且我也不想这么做,怕重新打开失败导致程序异常)。
示例程序:
#include <stdio.h> #include <windows.h> int main(int args, char ** argv) { FILE * fpOut = fopen("my_log_file.txt", "w"); if (fpOut) { for (int i=0; i<1000; i++) { fprintf(fpOut, "Event %i occurred\n", i); fflush(fpOut); Sleep(1000); // 这里能调用什么让文件大小对外可见? } fclose(fpOut); } return 0; }
解决方案
方法1:基于C标准库+Windows API刷新元数据
核心思路是通过Windows API强制刷新文件的元数据和目录缓存,让dir命令能立即获取到正确的文件大小。修改后的代码如下:
#include <stdio.h> #include <windows.h> #include <io.h> // 用于_get_osfhandle int main(int args, char ** argv) { FILE * fpOut = fopen("my_log_file.txt", "w"); if (fpOut) { // 从FILE*获取底层Windows文件句柄 HANDLE hFile = (HANDLE)_get_osfhandle(_fileno(fpOut)); if (hFile == INVALID_HANDLE_VALUE) { fclose(fpOut); return 1; } for (int i=0; i<1000; i++) { fprintf(fpOut, "Event %i occurred\n", i); fflush(fpOut); // 刷新文件元数据并更新目录缓存 LARGE_INTEGER currentSize; if (GetFileSizeEx(hFile, ¤tSize)) { // 重置文件指针到末尾,触发元数据同步 SetFilePointerEx(hFile, currentSize, NULL, FILE_BEGIN); SetEndOfFile(hFile); // 强制将所有数据和元数据写入磁盘 FlushFileBuffers(hFile); // 通过查询文件信息更新目录缓存 WIN32_FIND_DATA fileData; HANDLE hFind = FindFirstFile("my_log_file.txt", &fileData); if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); } } Sleep(1000); } fclose(fpOut); } return 0; }
方法2:直接使用Windows API操作文件
如果可以替换掉C标准库的文件操作,直接用Windows原生API能更精准控制缓存行为,代码如下:
#include <windows.h> int main(int args, char ** argv) { // 创建可共享读写的文件句柄,允许其他进程读取 HANDLE hFile = CreateFile("my_log_file.txt", GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); if (hFile != INVALID_HANDLE_VALUE) { for (int i=0; i<1000; i++) { char buffer[64]; sprintf(buffer, "Event %i occurred\n", i); DWORD bytesWritten; WriteFile(hFile, buffer, strlen(buffer), &bytesWritten, NULL); // 刷新数据和元数据到磁盘 FlushFileBuffers(hFile); // 更新目录缓存 WIN32_FIND_DATA fileData; HANDLE hFind = FindFirstFile("my_log_file.txt", &fileData); if (hFind != INVALID_HANDLE_VALUE) { FindClose(hFind); } Sleep(1000); } CloseHandle(hFile); } return 0; }
原理说明
FlushFileBuffers():强制把文件的缓存数据和元数据写入磁盘,但仅这一步还不够,因为系统的目录缓存可能没同步;FindFirstFile():主动读取文件的目录信息,触发系统更新目录缓存,这样后续的dir命令就能拿到最新的文件大小;- 方法1中的
SetEndOfFile():确保文件大小的元数据被正确同步,哪怕写入的数据刚好填满缓存块,也能触发更新。
内容的提问来源于stack exchange,提问作者Jeremy Friesner
相关产品推荐
相关产品推荐

