Отримайте розмір файлу на диску


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

Це дає логічний розмір файлу, а не розмір на диску.

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

Він повинен давати правильний розмір, в тому числі для:

  • Стислий файл
  • Розріджений файл
  • Фрагментований файл

Відповіді:


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);

Ви впевнені, що це правильно, якщо (result == 0) кидає новий Win32Exception (результат);
Саймон

Біт 'if (result == 0)' правильний (див. Msdn ), але ви маєте рацію, що я використовую неправильний конструктор. Я зараз це виправлю.
margnus1

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. : |
Steve Johnson

1
Я написав свій код на машині Vista x64, а тепер протестував його на машині W7 x64 у 64-розрядному режимі та режимі WOW64. Зверніть увагу, що GetDiskFreeSpace повинен повертати ненульове значення при успіху .
margnus1

1
Оригінальне запитання задає C #
Shane Courtrille

4
Цей код навіть не компілюється (одна заключна дужка відсутня при використанні), а одна підкладка дуже жахлива для навчальних цілей
Мікаель В.

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для обліку стиснених та / або розріджених файлів.
Ганс Олссон,

-1

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

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

Я все ще роблю тестування для цього, щоб отримати підтвердження.


7
Це розмір файлу (кількість байтів у файлі). Залежно від розміру блоків фактичного обладнання, файл може зайняти більше місця на диску. Наприклад, файл розміром 600 байт на моєму жорсткому диску використовував 4 кБ на диску. Отже, ця відповідь неправильна.
0xBADF00D
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.