我们在操作文件时,经常需要获取文件的大小。相信大家都知道 FileInfo 类有一个 Length 属性可以获取文件大小,但实际上我们还有一些别的方式,并且其他方式可能比 FileInfo 有更好的性能。这篇文章我们就来盘点一下 C# 中获取文件大小的几种方式,并简单比较一下它们的性能。
使用 FileInfo.Length ¶
这个是最常见的方式,FileInfo 类提供了一个 Length 属性,可以直接获取文件的大小,单位是字节(bytes)。
| |
这种方式的优点是代码简洁易懂,并且几乎适用于所有场景,包括跨平台开发。它唯一美中不足的地方在于,FileInfo 在使用前需要实例化,这会带来一点 GC 开销。
另外,如果觉得返回的 long 类型不够直观,我们也可以将其转换为更常见的单位,比如 KB、MB、GB 等。对于这个需求,除了自己写转换代码,我们还可以使用 Humanizer 这个库,它提供了非常方便的文件大小格式化功能。
| |
使用 RandomAccess ¶
RandomAccess 是一个在。NET 6 中引入的静态类,旨在提供高性能、线程安全的文件随机访问 I/O 操作。它提供的 GetLength 方法可以直接获取文件的大小。但稍微有些可惜的是,虽然它的 GetLength 方法是静态且不需要创建对象的,但它需要传入一个文件句柄(file handle),而后者是一个 SafeFileHandle 对象,这就不可避免地引入了 FileStream 对象的创建开销。
| |
那么实际上这个方式的效果怎么样呢?在后面的跑分环节会揭晓答案。
使用 P/Invoke 调用 Windows API ¶
当程序运行的平台是 Windows 时,我们还可以通过 P/Invoke 调用 Windows API 来获取文件大小。这个方式的好处是它不需要创建任何托管对象,因此理论上它的性能应该是最好的。
| |
然后我们就可以使用上面的 GetFileSizeWin32 方法来获取文件大小了。
Mono.Posix 之类的库来调用系统 API,但这样做的复杂度和维护成本会比较高,所以推荐直接使用 FileInfo。使用 VisualBasic.FileIO ¶
最后这种方式可能有凑数的嫌疑,但是我们确实可以借助 Microsoft.VisualBasic 命名空间下的 FileSystem 类来获取文件大小。
| |
性能比较 ¶
以上就是四种可行的获取文件大小的方式。接下来我们来比较一下它们的性能。结果如下:
| Method | Mean | Error | StdDev | Allocated |
|---|---|---|---|---|
| UseFileInfo | 7.888 μs | 0.0522 μs | 0.0462 μs | 96 B |
| UseWin32Api | 7.732 μs | 0.0740 μs | 0.0692 μs | - |
| UseFileSystem | 15.779 μs | 0.2104 μs | 0.1968 μs | 96 B |
| UseRandomAccess | 10.627 μs | 0.1137 μs | 0.1063 μs | 72 B |
从结果中我们不难得出以下几个结论:
- 使用 Windows API 的方式性能最好,并且没有任何托管内存分配。但它只能在 Windows 平台使用。
- 使用
FileInfo的方式性能也不错,适用于绝大多数场景。 - 使用
RandomAccess的方式性能一般,虽然它不需要创建FileInfo对象,但它需要创建FileStream对象来获取文件句柄,这带来了无法避免的开销(虽然比FileInfo少了一点)。 - 使用
VisualBasic.FileIO的方式性能最差,并且还会有内存分配,基本上没有任何优势。
总结 ¶
综上所述,几乎在任何情况下,我们都可以优先考虑使用 FileInfo.Length 来获取文件大小。只有在对于性能有极致要求,并且程序运行的平台确定是 Windows 的情况下,才考虑使用 P/Invoke 调用 Windows API 的方式。
