不使用System.IO.FileInfo如何获取文件大小?

14

在不使用System.IO.FileInfo的情况下,是否可以在C#中获取文件的大小

我知道可以通过Path.GetFileName(yourFilePath)Path.GetExtension(yourFilePath)获取其他信息,例如名称和扩展名,但显然无法获取文件大小?有没有其他方法可以获取文件大小而不使用System.IO.FileInfo?

唯一的原因是,如果我没记错的话,FileInfo获取的信息比我实际需要的要多,因此如果我只需要文件的大小,那么收集所有这些FileInfo需要更长时间。有更快的方法吗?


22
过早优化是万恶之源。使用 FileInfo,对代码进行性能分析,以确定其是否足够快。如果您已经确认它在应用程序运行时间中占据了相当大的比例,并且您的应用程序速度不可接受,那么考虑其他选项。 - Servy
1
我想象文件大小占据了大部分时间,而其他项目基本上是免费附带的。 - asawyer
2
过早的优化是万恶之源。这真的给你带来了问题吗? - Kevin
4
FileInfo一个众所周知的问题是它只获取你请求的数据。但目前来说还是相当方便的,因此试图优化它是没有意义的。 - Hans Passant
1
@Servy的要求无法提供您所需的可能性。我知道您的意图,但OP想确定BAU,他们应该期望什么。如果OP知道FileInfo通常是15%的开销,没有优化X,我相信这就是他们想要的。 - Aaron McIver
显示剩余7条评论
6个回答

10

我使用这两种方法进行了基准测试:

    public static uint GetFileSizeA(string filename)
    {
        WIN32_FIND_DATA findData;
        FindFirstFile(filename, out findData);
        return findData.nFileSizeLow;
    }

    public static uint GetFileSizeB(string filename)
    {
        IntPtr handle = CreateFile(
            filename,
            FileAccess.Read,
            FileShare.Read,
            IntPtr.Zero,
            FileMode.Open,
            FileAttributes.ReadOnly,
            IntPtr.Zero);
        long fileSize;
        GetFileSizeEx(handle, out fileSize);
        CloseHandle(handle);
        return (uint) fileSize;
    }

运行在超过2300个文件上,GetFileSizeA花费62-63毫秒运行。GetFileSizeB花费超过18秒。

除非有人看到我做错了什么,否则我认为答案很明显,哪种方法更快。

有没有方法可以避免实际打开文件?

更新

将FileAttributes.ReadOnly更改为FileAttributes.Normal可减少计时,使得两种方法的表现相同。

此外,如果跳过CloseHandle()调用,GetFileSizeEx方法将变快约20-30%,但我不知道是否推荐这样做。


可以使用FindFirstFileEx进一步改进,并在可能的情况下限制搜索。 - SmartK8
@SmartK8 你说的限制是什么意思?它正在搜索特定的文件名。在基准测试中,我得到了目录中所有文件的列表,然后使用完整的路径和文件名调用GetFileSizeA()或GetFileSizeB()函数。 - Pete
我认为OP所说的“收集所有这些需要更长时间”是指他正在获取更多的文件。他并没有必要在一个目录中只获取一个文件。因此,我想指出,我同意他应该在这种情况下使用FindFirstFile(FindNextFile)。或者可能是FindFirstFileEx。因为它提供了更多的选项来指定搜索选项(仅文件夹、大型获取等)。 - SmartK8
@Pete:当我尝试测试你建议的方法时,我遇到了一些引用错误。我需要调用特定的库吗?谢谢。 - sergeidave
@sergeidave 哎呀,代码已修复。将句柄更改为IntPtr即可。使用SafeHandle会有额外的开销,因为需要释放它。 - Pete
@Pete:请问你能告诉我在哪里可以找到 'FindFirstFile()', 'CreateFile()', 'GetFileSizeEx()', 'CloseHandle()' 的完整函数吗?我想在C#中使用你的代码。 - Koder101

6

从我做的一个简短测试中,我发现使用 FileStream 平均比使用 Pete 的 GetFileSizeB 慢了 1 毫秒(在网络共享上大约花费了 21 毫秒...)。个人而言,在能够的情况下,我更喜欢留在 BCL 限制内。

代码很简单:

using (var file = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
    return file.Length;
}

在我的情况下,这段代码比使用 FileInfo 更慢吗? - sa.he

3

这不是直接的答案...因为我不确定是否有更快的方法可以使用.NET框架。

这是我正在使用的代码:

  List<long> list = new List<long>();
  DirectoryInfo di = new DirectoryInfo("C:\\Program Files");
  FileInfo[] fiArray = di.GetFiles("*", SearchOption.AllDirectories);
  foreach (FileInfo f in fiArray)
    list.Add(f.Length);

在我的“Program Files”目录下运行该程序,大约有22720个文件,共计运行了2709毫秒。这个速度是相当不错的。此外,当我将*.txt作为GetFiles方法的第一个参数进行筛选时,时间缩短到了461毫秒。

很多因素会影响程序运行速度,其中硬盘的速度是很重要的因素,但我并不认为 FileInfo 会对性能造成太大的负面影响。

注意:我认为这只适用于 .NET 4 及以上版本。


2
这与问题有何关联?你正在使用原帖作者不想使用的方法。 - aqua
1
@aqua 这是相关的,因为它展示了使用FileInfo可能不会显著降低性能。 - Jeff Johnson
2
这是处理本地文件时的情况。当处理网络文件时,使用 FileInfo 会很慢。有一个名为 FastFileInfo 的 codeproject 可以解决这个问题。 - Loathing
@Loathing 非常感谢!FastFileInfo对我很有帮助,因为我通过网络获取了23.5k个FileInfos,需要20分钟的时间。现在只需要1分09秒O.o!这太棒了! - Keenora Fluffball
@KeenoraFluffball 不错的东西。Codeproject 上的一个递归异常,我重写了它并放在了 sourceforge.net 上,而且 sourceforge 版本应该也会更快一些。 - Loathing

3
根据这条评论

我有一个小应用程序,它收集大小信息并将其保存到数组中...但我经常有大约50万个文件,这需要花费一段时间去遍历所有这些文件(我正在使用FileInfo)。 我只是想知道是否有更快的方法...

由于您要查找如此多文件的大小,与通过其他方式获取文件大小相比,您更有可能从并行化中受益。 FileInfo类应该足够好,任何改进都可能很小。
另一方面,并行化文件大小请求具有在速度上显著提高的潜力。 (请注意,改进程度将在很大程度上基于您的磁盘驱动器而不是处理器,因此结果可能会有很大差异。)

实际上,如果他正在从各个目录中收集大量文件,他可能会受益于使用FindFirstFile和FindNextFile()来迭代目录中的文件,尽管我没有数字来支持这一点。 - Pete

1

如果您想在非Windows主机上使用.NET Core或Mono运行时进行此操作,可以采用快速且不太完美的解决方案:

包含Mono.Posix.NETStandard NuGet包,然后像这样进行操作...

using Mono.Unix.Native;

private long GetFileSize(string filePath)
{
    Stat stat;
    Syscall.stat(filePath, out stat);
    return stat.st_size;
}

我已经在Linux和macOS上测试了在.NET Core上运行 - 不确定它是否适用于Windows - 鉴于这些都是底层的POSIX系统调用(并且该包由Microsoft维护),它可能会起作用。 如果不行,可以结合其他基于P / Invoke的答案来覆盖所有平台。
FileInfo.Length相比,当获取正在被另一个进程/线程写入的文件的大小时,这给我提供了更可靠的结果。

0
你可以尝试这个:
[DllImport("kernel32.dll")]
static extern bool GetFileSizeEx(IntPtr hFile, out long lpFileSize);

但这并没有太大的改善...

这是从pinvoke.net中取出的示例代码:

IntPtr handle = CreateFile(
    PathString, 
    GENERIC_READ, 
    FILE_SHARE_READ, 
    0, 
    OPEN_EXISTING, 
    FILE_ATTRIBUTE_READONLY, 
    0); //PInvoked too

if (handle.ToInt32() == -1) 
{
    return; 
}

long fileSize;
bool result = GetFileSizeEx(handle, out fileSize);
if (!result) 
{
    return;
}

1
@Venson:我很抱歉地说,这绝对比你的好;) - edeboursetty
当然,但这就是我说的!那只是一个想法,请在投票之前阅读! - Venson
@Venson 不,那不是你说的。你说它们是一样的。他们不是。你的很糟糕,这可能更烦人,但可能不会(至少不会太糟)。 - Servy
@sergeidave 两种最好的方法是GetFileSizeEx()(如上所述)和FindFirstFile(这就是FileInfo使用的方法)。我不知道其中一种是否有特定的性能优势。但如果性能真的很关键,您应该计时这两种方法并查看哪种实际上更快。使用FileInfo可能同样快。 - Pete
3
这与FileStream.Length相同,只是不太易读的版本。 - Tim Schmelter
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接