Uzyskaj rozmiar pliku na dysku


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

Daje to logiczny rozmiar pliku, a nie rozmiar na dysku.

Chciałbym uzyskać rozmiar pliku na dysku w C # (najlepiej bez międzyoperacyjności ), zgodnie z raportem Eksploratora Windows.

Powinien podawać prawidłowy rozmiar, w tym dla:

  • Skompresowany plik
  • Rzadki plik
  • Pofragmentowany plik

Odpowiedzi:


50

Używa GetCompressedFileSize, jak sugerował ho1, a także GetDiskFreeSpace, jak zasugerował PaulStack, jednak używa P / Invoke. Przetestowałem go tylko dla plików skompresowanych i podejrzewam, że nie działa dla plików pofragmentowanych.

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

czy jesteś pewien, że to jest poprawne, jeśli (result == 0) rzuca nowy Win32Exception (wynik);
— Simon

Bit „if (result == 0)” jest poprawny (patrz msdn ), ale masz rację, że używam niewłaściwego konstruktora. Naprawię to teraz.
— margnus1

FileInfo.Directory.Rootnie wygląda na to, żeby mógł obsłużyć jakikolwiek rodzaj linków do systemu plików. Działa więc tylko na klasycznych literach dysków lokalnych bez dowiązań symbolicznych / twardych / punktów połączeń lub czegokolwiek, co ma do zaoferowania NTFS.
— ygoe

Czy ktoś mógłby wyjaśnić krok po kroku, co zostało zrobione na różnych etapach? Bardzo pomocne będzie zrozumienie, jak to właściwie działa. Dzięki.
— bapi

5
Ten kod wymaga przestrzeni nazw System.ComponentModeli System.Runtime.InteropServices.
— Kenny Evitt,

17

Powyższy kod nie działa poprawnie w systemie Windows Server 2008 systemy R2 lub Windows 7 i Windows Vista opartych lub 2008 jako rozmiar klastra jest zawsze zero (GetDiskFreeSpaceW i GetDiskFreeSpace powrót -1 nawet z UAC wyłączone.) Oto zmodyfikowany kod, który działa.

DO#

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

Aby ten kod działał, wymagane jest odwołanie do System.Managment. Wygląda na to, że nie ma standardowego sposobu dokładnego określenia rozmiaru klastra w systemie Windows (wersje 6.x), z wyjątkiem WMI. : |
— Steve Johnson,

1
Napisałem swój kod na maszynie z Vista x64, a teraz przetestowałem go na maszynie W7 x64 w trybie 64-bitowym i WOW64. Zauważ, że GetDiskFreeSpace powinien zwrócić wartość niezerową w przypadku sukcesu .
— margnus1

1
Oryginalne pytanie dotyczy C #
— Shane'a Courtrille'a

4
Ten kod nawet się nie kompiluje (brakuje jednego nawiasu zamykającego w użyciu), a jedna linijka jest bardzo okropna do celów edukacyjnych
— Mickael V.

1
Ten kod ma również problem z kompilacją podczas żądania, .First()ponieważ jest to IEnumerablea nie an IEnumerable<T>, jeśli chcesz użyć kodu pierwsze wywołanie.Cast<object>()
— yoel halb

5

Według forów społecznościowych MSDN:

Rozmiar na dysku powinien być sumą rozmiaru klastrów przechowujących plik: aby znaleźć rozmiar klastra,
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
musisz zagłębić się w P / Invoke ; GetDiskFreeSpace()zwraca go.

Zobacz, jak uzyskać rozmiar na dysku pliku w języku C # .

Należy jednak pamiętać o tym, że nie zadziała to w systemie plików NTFS, w którym włączona jest kompresja.


2
Sugeruję GetCompressedFileSizeraczej użycie czegoś takiego niż filelengthuwzględnienie skompresowanych i / lub rzadkich plików.
— Hans Olsson

-1

Myślę, że będzie tak:

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

Wciąż przeprowadzam testy, aby uzyskać potwierdzenie.


7
To jest rozmiar pliku (liczba bajtów w pliku). W zależności od rozmiarów bloków rzeczywistego sprzętu plik może zajmować więcej miejsca na dysku. Np. Plik o rozmiarze 600 bajtów na moim dysku twardym zajmował 4kB na dysku. Więc ta odpowiedź jest nieprawidłowa.
— 0xBADF00D
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.