Получить размер файла на диске

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

Это дает логический размер файла, а не размер на диске.

Я хочу получить размер файла на диске на C # (желательно без взаимодействия ), как будет сообщать проводник Windows.

Он должен иметь правильный размер, в том числе для:

  • Сжатый файл
  • Редкий файл
  • Фрагментированный файл
Wernight
источник

Ответы:

50

Здесь используется GetCompressedFileSize, как предлагал ho1, а также GetDiskFreeSpace, как предлагал PaulStack, однако он использует 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);
margnus1
источник
вы уверены, что это правильно, если (результат == 0) выбросить новое исключение Win32Exception (результат);
Саймон
Бит if (result == 0) правильный (см. Msdn ), но вы правы, что я использую неправильный конструктор. Сейчас исправлю.
margnus1 01
FileInfo.Directory.Rootне похоже, что он может обрабатывать любые ссылки на файловую систему. Таким образом, он работает только с классическими буквами локальных дисков без символических ссылок / жестких ссылок / точек соединения или чего-либо еще, что может предложить NTFS.
ygoe
Может ли кто-нибудь дать пошаговое объяснение, что было сделано на разных этапах? Будет очень полезно понять, как это работает на самом деле. Спасибо.
bapi
5
Для этого кода требуются пространства имен System.ComponentModelи System.Runtime.InteropServices.
Кенни Эвитт
17

Приведенный выше код не работает должным образом в системах на базе Windows Server 2008 или 2008 R2 или Windows 7 и Windows Vista, поскольку размер кластера всегда равен нулю (GetDiskFreeSpaceW и GetDiskFreeSpace возвращают -1 даже при отключенном UAC ). Вот модифицированный код, который работает.

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.Managment. Похоже, что стандартного способа точного определения размера кластера в Windows (версии 6.x), кроме WMI, не существует. : |
Стив Джонсон,
1
Я написал свой код на машине Vista x64, а теперь протестировал его на машине W7 x64 в 64-битном и WOW64 режиме. Обратите внимание, что GetDiskFreeSpace должен возвращать ненулевое значение в случае успеха .
margnus1 01
1
Исходный вопрос задает C #
Шейн Кортрилл
4
Этот код даже не компилируется (одна закрывающая скобка отсутствует при использовании), а один лайнер очень ужасен для учебных целей
Микаэль В.
1
У этого кода также есть проблема с компиляцией при запросе, .First()поскольку он является, IEnumerableа не является IEnumerable<T>, если вы хотите использовать первый вызов кода.Cast<object>()
Йоэль Халб 08
5

Согласно социальным форумам MSDN:

Размер на диске должен быть суммой размеров кластеров, в которых хранится файл:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
вам нужно окунуться в P / Invoke, чтобы найти размер кластера; GetDiskFreeSpace()возвращает его.

См. Раздел Как получить размер файла на диске в C # .

Но учтите, что это не будет работать в NTFS, где включено сжатие.

стек72
источник
2
Я предлагаю использовать что-то вроде, GetCompressedFileSizeа не filelengthучитывать сжатые и / или разреженные файлы.
Ханс Олссон,
-1

Думаю будет так:

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

Я все еще проверяю это, чтобы получить подтверждение.

бапи
источник
7
Это размер файла (количество байтов в файле). В зависимости от размеров блоков фактического оборудования файл может занимать больше места на диске. Например, файл размером 600 байт на моем жестком диске использовал 4 КБ на диске. Так что это неверный ответ.
0xBADF00D