获取磁盘上文件的大小

89
var length = new System.IO.FileInfo(path).Length;

这提供了文件的逻辑大小,而不是磁盘上的大小。

我希望在C#中获取文件在磁盘上的大小(最好不使用interop),就像Windows资源管理器报告的那样。

它应该能正确地给出包括以下情况的大小:

  • 压缩文件
  • 稀疏文件
  • 分段文件
5个回答

55

按照ho1的建议,此代码使用GetCompressedFileSize和PaulStack所建议的GetDiskFreeSpace,但它需要使用P/Invoke。我只对压缩文件进行了测试,我怀疑它无法用于碎片文件。

public static long GetFileSizeOnDisk(string file)
{
    FileInfo info = new FileInfo(file);
    uint dummy, sectorsPerCluster, bytesPerSector;
    int result = GetDiskFreeSpaceW(info.Directory.Root.FullName, out sectorsPerCluster, out bytesPerSector, out dummy, out dummy);
    if (result == 0) throw new Win32Exception();
    uint clusterSize = sectorsPerCluster * bytesPerSector;
    uint hosize;
    uint losize = GetCompressedFileSizeW(file, out hosize);
    long size;
    size = (long)hosize << 32 | losize;
    return ((size + clusterSize - 1) / clusterSize) * clusterSize;
}

[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
   [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

[DllImport("kernel32.dll", SetLastError = true, PreserveSig = true)]
static extern int GetDiskFreeSpaceW([In, MarshalAs(UnmanagedType.LPWStr)] string lpRootPathName,
   out uint lpSectorsPerCluster, out uint lpBytesPerSector, out uint lpNumberOfFreeClusters,
   out uint lpTotalNumberOfClusters);

FileInfo.Directory.Root 看起来似乎无法处理任何类型的文件系统链接。因此,它仅适用于经典本地驱动器字母,没有符号链接/硬链接/连接点或 NTFS 提供的任何其他内容。 - ygoe
有没有人可以请逐步解释一下,不同步骤都做了什么?这将非常有帮助,以便理解它的实际工作原理。谢谢。 - bapi
5
该代码需要引用命名空间 System.ComponentModelSystem.Runtime.InteropServices - Kenny Evitt
user\appdata\local\microsoft\windowsapps\Microsoft.DesktopAppInstaller_somekey\... 文件夹中发现了一个奇怪的 bug。所有文件在 Windows 资源管理器中都显示为 0 磁盘大小,但是使用这个方法却显示了一个很大的大小。 - Daniel Möller
我用我的代码进行了测试。但是它为文件夹中的每个文件提供相同的值。@margnus1,你知道原因吗? - Kalpani Ranasinghe
显示剩余3条评论

17
上面的代码在基于Windows Server 2008或2008 R2、Windows 7和Windows Vista的系统上无法正常工作,因为簇大小始终为零(即使禁用了UAC,GetDiskFreeSpaceW和GetDiskFreeSpace仍返回-1)。这是可以正常工作的修改后的代码。 C#
public static long GetFileSizeOnDisk(string file)
{
    FileInfo info = new FileInfo(file);
    uint clusterSize;
    using(var searcher = new ManagementObjectSearcher("select BlockSize,NumberOfBlocks from Win32_Volume WHERE DriveLetter = '" + info.Directory.Root.FullName.TrimEnd('\\') + "'") {
        clusterSize = (uint)(((ManagementObject)(searcher.Get().First()))["BlockSize"]);
    }
    uint hosize;
    uint losize = GetCompressedFileSizeW(file, out hosize);
    long size;
    size = (long)hosize << 32 | losize;
    return ((size + clusterSize - 1) / clusterSize) * clusterSize;
}

[DllImport("kernel32.dll")]
static extern uint GetCompressedFileSizeW(
   [In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName,
   [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);

VB.NET

  Private Function GetFileSizeOnDisk(file As String) As Decimal
        Dim info As New FileInfo(file)
        Dim blockSize As UInt64 = 0
        Dim clusterSize As UInteger
        Dim searcher As New ManagementObjectSearcher( _
          "select BlockSize,NumberOfBlocks from Win32_Volume WHERE DriveLetter = '" + _
          info.Directory.Root.FullName.TrimEnd("\") + _
          "'")

        For Each vi As ManagementObject In searcher.[Get]()
            blockSize = vi("BlockSize")
            Exit For
        Next
        searcher.Dispose()
        clusterSize = blockSize
        Dim hosize As UInteger
        Dim losize As UInteger = GetCompressedFileSizeW(file, hosize)
        Dim size As Long
        size = CLng(hosize) << 32 Or losize
        Dim bytes As Decimal = ((size + clusterSize - 1) / clusterSize) * clusterSize

        Return CDec(bytes) / 1024
    End Function

    <DllImport("kernel32.dll")> _
    Private Shared Function GetCompressedFileSizeW( _
        <[In](), MarshalAs(UnmanagedType.LPWStr)> lpFileName As String, _
        <Out(), MarshalAs(UnmanagedType.U4)> lpFileSizeHigh As UInteger) _
        As UInteger
    End Function

此代码需要 System.Management 引用才能正常工作。在 Windows(6.x 版本)上,似乎没有标准的方法可以准确地获取集群大小,除了使用 WMI。 :| - Steve Johnson
1
我在一台Vista x64机器上编写了代码,现在在W7 x64机器上以64位和WOW64模式进行了测试。请注意,GetDiskFreeSpace应该在成功时返回非零值。 - margnus1
1
原始问题要求使用C#。 - Shane Courtrille
5
这段代码甚至无法编译(using语句缺少一个闭合括号),而且这个单行代码对于学习目的来说非常糟糕。 - Mickael V.
1
当请求.First()时,此代码也存在编译问题,因为它是IEnumerable而不是IEnumerable<T>。如果要使用该代码,请先调用.Cast<object>() - yoel halb

5
根据MSDN社区论坛:
磁盘上的大小应该是存储文件的簇大小之和:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
您需要深入了解P/Invoke以查找簇大小;GetDiskFreeSpace()返回它。
请参见如何在C#中获取文件的磁盘大小
但请注意,这在启用压缩的NTFS中无法正常工作。

2
我建议使用类似于GetCompressedFileSize而不是filelength来处理压缩和/或稀疏文件。 - Hans Olsson

0

如果您从kernal32.dll调用外部函数GetCompressedFileSizeW,则不需要知道块大小。该函数将始终返回块大小的倍数。

唯一需要块大小的情况是,如果您试图手动计算未压缩/稀疏的文件在磁盘上的大小,并且仅知道文件的长度。然后,您可以使用long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize)手动计算磁盘上的大小(如果filelength mod blocksize不等于0,则实质上将其四舍五入到下一个块大小的倍数)。根据Microsoft文档,此公式不适用于GetCompressedFileSizeW,该函数返回用于存储文件的字节大小。

using System.Runtime.InteropServices;

class GetSize
{
    public static ulong GetFileSizeOnDisk(string file)
    {
        uint low_bytes = GetCompressedFileSizeW(file, out uint high_bytes);
        ulong size = ((ulong)high_bytes << 32) | low_bytes;

        return size;
    }

    [DllImport("kernel32.dll")]
    static extern uint GetCompressedFileSizeW([In, MarshalAs(UnmanagedType.LPWStr)] string lpFileName, [Out, MarshalAs(UnmanagedType.U4)] out uint lpFileSizeHigh);


    public static void Main()
    {
        string file = @"C:\yourfile.txt";
        Console.WriteLine(GetFileSizeOnDisk(file));
    }
}

我已经测试了稀疏文件和压缩文件,并且上述代码始终返回资源管理器报告的磁盘上的相同大小。


-3

我认为它会是这样的:

double ifileLength = (finfo.Length / 1048576); //return file size in MB ....

我还在进行一些测试,以获得确认。


8
这是文件的大小(文件内的字节数)。根据实际硬件的块大小,文件可能会占用更多的磁盘空间。例如,我的硬盘上有一个600字节的文件,在磁盘上使用了4kB。因此,答案是不正确的。 - 0xBADF00D

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