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
Wernight
źródło

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);
margnus1
źródło
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
Steve Johnson
źródło
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.

stos72
źródło
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.

bapi
źródło
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