【问题标题】:Check the file-size without opening file in C++?检查文件大小而不用 C++ 打开文件?
【发布时间】:2012-02-17 22:58:07
【问题描述】:

我正在尝试获取大文件 (12gb+) 的文件大小,但我不想打开文件这样做,因为我认为这会占用大量资源。有什么好的API可以做到这一点吗?我在 Windows 环境中。

【问题讨论】:

    标签: c++ windows winapi


    【解决方案1】:

    您应该调用GetFileSizeEx,它比旧的GetFileSize 更易于使用。您需要通过调用CreateFile 打开文件,但这是一个廉价的操作。您认为打开一个文件(即使是 12GB 文件)代价高昂的假设是错误的。

    您可以使用以下函数来完成工作:

    __int64 FileSize(const wchar_t* name)
    {
        HANDLE hFile = CreateFile(name, GENERIC_READ, 
            FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 
            FILE_ATTRIBUTE_NORMAL, NULL);
        if (hFile==INVALID_HANDLE_VALUE)
            return -1; // error condition, could call GetLastError to find out more
    
        LARGE_INTEGER size;
        if (!GetFileSizeEx(hFile, &size))
        {
            CloseHandle(hFile);
            return -1; // error condition, could call GetLastError to find out more
        }
    
        CloseHandle(hFile);
        return size.QuadPart;
    }
    

    还有其他 API 调用会返回文件大小,而不会强制您创建文件句柄,特别是 GetFileAttributesEx。然而,这个函数只是在幕后打开文件是完全合理的。

    __int64 FileSize(const wchar_t* name)
    {
        WIN32_FILE_ATTRIBUTE_DATA fad;
        if (!GetFileAttributesEx(name, GetFileExInfoStandard, &fad))
            return -1; // error condition, could call GetLastError to find out more
        LARGE_INTEGER size;
        size.HighPart = fad.nFileSizeHigh;
        size.LowPart = fad.nFileSizeLow;
        return size.QuadPart;
    }
    

    如果您使用 Visual Studio 进行编译并且希望避免调用 Win32 API,那么您可以使用_wstat64

    这是一个基于_wstat64的函数版本:

    __int64 FileSize(const wchar_t* name)
    {
        __stat64 buf;
        if (_wstat64(name, &buf) != 0)
            return -1; // error, could use errno to find out more
    
        return buf.st_size;
    } 
    

    如果性能曾经成为您的问题,那么您应该为您所针对的所有平台上的各种选项计时,以便做出决定。不要以为不需要您调用CreateFile 的API 会更快。他们可能是,但你不会知道,直到你计时。

    【讨论】:

    • 当然,CreateFile() can be rather slow if you're opening the file on slow media 喜欢网络驱动器,但速度慢是由于存储访问延迟,而不是因为文件很大。
    • @Insilico 或磁带驱动器!但我相信打开文件是找到文件大小的唯一方法,至少在 Windows 上是这样。
    • @DavidHeffernan:不!文件大小在标题中,因此在目录中。如下所示的 FindFirstFile() 将读取该信息而无需打开文件。
    • @Alexis 阅读 Raymond 的文章以了解详细信息。元数据包含大小的副本,但它可能已过时。真实大小在文件中。 blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
    • 软盘驱动器和损坏的 CD 也是慢速媒体。此外,您可能要枚举数以千计的非大量文件,并且必须打开和关闭每个文件才能获得大小是很麻烦的,特别是因为大小已经存储在可以/应该缓存在内存中的目录条目中; FAT(32) 和 CDFS 仍然不错的另一个原因。
    【解决方案2】:

    我还担心打开文件和关闭文件只是为了获得它的大小而付出的代价。并决定询问performance counter^,看看这些操作到底有多贵是。

    这是使用三种方法对同一文件执行 1 次文件大小查询所花费的周期数。在 2 个文件上进行了测试:150 MB 和 1.5 GB。有 +/- 10% 的波动,因此它们似乎不受实际文件大小的影响。 (显然这取决于 CPU,但它为您提供了一个很好的优势)

    • 190 个周期 - CreateFile, GetFileSizeEx, CloseHandle
    • 40 个周期 - GetFileAttributesEx
    • 150 个周期 - FindFirstFile, FindClose

    The GIST with the code used^ 在此处可用

    正如我们从这个高度科学的:) 测试中看到的,最慢的实际上是文件打开器。第二慢的是文件查找器,而获胜者是属性读取器。 现在,就可靠性而言,CreateFile 应该优于其他 2 个。 但我仍然不喜欢打开文件只是为了读取文件大小的概念......除非我是做大小关键的事情,我会选择属性

    PS当我有时间时,我会尝试读取已打开和正在写入的文件的大小。但不是现在……

    【讨论】:

    • 关于您的 PS:看起来 GetFileAttributesEx() 实际上确实返回了正确的文件大小,而文件仍在由另一个进程更新,使其最快(正确的文件大小)选择。如果它只有最后一次文件更改时间(不要与最后一次写入时间混淆),那么这个功能就完美了!
    • @MichaelGoldshteyn 您在上述评论中提到的最后一次文件更改时间到底是多少?这次还有其他 API 可以获取吗?
    • 很高兴看到一些数字,但我怀疑真正的问题是每个涉及多少 IO。目前尚不清楚它们在这方面是否不同。
    【解决方案3】:

    使用FindFirstFile 函数的另一个选项

    #include "stdafx.h"
    #include <windows.h>
    #include <tchar.h>
    #include <stdio.h>
    
    int _tmain(int argc, _TCHAR* argv[])
    {
       WIN32_FIND_DATA FindFileData;
       HANDLE hFind;
       LPCTSTR  lpFileName = L"C:\\Foo\\Bar.ext";
    
       hFind = FindFirstFile(lpFileName , &FindFileData);
       if (hFind == INVALID_HANDLE_VALUE) 
       {
          printf ("File not found (%d)\n", GetLastError());
          return -1;
       } 
       else 
       {
          ULONGLONG FileSize = FindFileData.nFileSizeHigh;
          FileSize <<= sizeof( FindFileData.nFileSizeHigh ) * 8; 
          FileSize |= FindFileData.nFileSizeLow;
          _tprintf (TEXT("file size is %u\n"), FileSize);
          FindClose(hFind);
       }
       return 0;
    
    }
    

    【讨论】:

    • 使用ULARGE_INTEGER 而不是手动旋转ULONGLONG 位,例如:ULARGE_INTEGER ul; ul.LowPart = FindFileData.nFileSizeLow; ul.HighPart = FindFileData.nFileSizeHigh; ULONGLONG FileSize = ul.QuadPart;。此外,%u 在 Windows 上需要 32 位 unsigned int,您需要使用 %Lu 代替 64 位整数。
    • 我相信 FindFirstFile 检索目录条目中记录的文件大小。请注意,在某些情况下,这可能不准确,例如,如果文件是硬链接并通过不同的硬链接进行了修改,或者另一个应用程序打开了文件并对其进行了修改。见blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx
    • Harry 指出的问题大概是为什么 Delphi RTL 停止在其文件大小 sys 函数中使用 FindFirstFile。
    • 此方法不适用于符号链接,它返回零。
    【解决方案4】:

    从 C++17 开始,file_size 作为标准库的一部分。 (然后实现者决定如何有效地完成它!)

    【讨论】:

      【解决方案5】:

      GetFileSize 函数呢?

      【讨论】:

      • 这需要打开文件,OP 说这是不可取的。
      • @remy 但文件是存储大小的位置,因此问题中的两个请求是矛盾的
      • 其实没有,文件本身不存储大小。文件系统存储它。 GetFileSize() 要求首先打开文件,然后使用该句柄确定文件在文件系统中的位置,以便获取大小。如果你改用FindFirstFile(),它会查询文件系统而不需要打开文件。
      • @Remy 不是 Raymond 所说的:blogs.msdn.com/b/oldnewthing/archive/2011/12/26/10251026.aspx 另外,如果您不使用 name,则不会有通知,所以您最终只能自言自语!
      猜你喜欢
      • 2015-08-15
      • 1970-01-01
      • 2013-08-08
      • 2011-04-24
      • 2014-07-28
      • 2017-03-31
      • 2011-12-16
      • 2012-02-09
      • 1970-01-01
      相关资源
      最近更新 更多