2010-09-20 35 views
73
var length = new System.IO.FileInfo(path).Length; 

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

Chciałbym uzyskać rozmiar pliku na dysku w C# (najlepiej bez interop), jak byłoby zgłaszane przez Eksploratora Windows.

To powinno dać odpowiedni rozmiar, w tym:

  • Skompresowany plik
  • rzadki plik
  • Rozproszony plik

Odpowiedz

39

Używa GetCompressedFileSize, jak zasugerował ho1, a także GetDiskFreeSpace, jak sugeruje PaulStack , jednak używa P/Invoke. Przetestowałem to tylko w przypadku plików skompresowanych i podejrzewam, że nie działa w przypadku fragmentacji plików.

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

czy jesteś pewien, że to jest poprawne if (result == 0) throw new Win32Exception (result); – Simon

+0

Bit "if (result == 0)" jest poprawny (patrz [msdn] (http://msdn.microsoft.com/en-us/library/aa364935.aspx)), ale masz rację, że jestem używając niewłaściwego konstruktora. Naprawię to teraz. – margnus1

+0

'FileInfo.Directory.Root' nie wygląda tak, jakby mógł obsłużyć dowolne linki systemu plików. Działa to wyłącznie na klasycznych dyskach lokalnych, bez dowiązań symbolicznych/twardych/punktów połączenia lub bez względu na to, co NTFS ma do zaoferowania. – ygoe

4

Zgodnie z MSDN forach społecznych:

Rozmiar na dysku powinien być sumą rozmiar klastrów, które przechowują plik:
http://social.msdn.microsoft.com/Forums/en-US/Vsexpressvcs/thread/85bf76ac-a254-41d4-a3d7-e7803c8d9bc3
Będziesz musiał zanurzyć się w P/Invoke, aby znaleźć rozmiar klastra; GetDiskFreeSpace() zwraca go.

Zobacz How to get the size on disk of a file in C#.

Należy jednak pamiętać, że nie zadziała to w przypadku, gdy kompresja jest włączona.

+1

Proponuję użyć czegoś w stylu 'GetCompressedFileSize' zamiast' filelength' do uwzględnienia skompresowanych i/lub rzadkich plików. –

14

Powyższy kod nie działa poprawnie na Windows Server 2008 lub 2008 R2 lub Windows 7 i Windows     systemów opartych Vista jak rozmiar klastra jest zawsze zero (GetDiskFreeSpaceW i GetDiskFreeSpace zwracają -1 nawet z UAC niepełnosprawnych). Oto zmodyfikowany kod, który działa.

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 
+1

Wersja Crack (w pełni działa)? – Norbert

+0

Tak .. Lolz ... –

+0

Odniesienie do pliku System.Managment jest wymagane, aby ten kod zadziałał. Wygląda na to, że nie ma standardowego sposobu uzyskania rozmiaru klastra w systemie Windows (wersje 6.x) z wyjątkiem WMI. : | –

0

myślę to będzie tak:

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

ja wciąż robi jakieś badania na ten , aby uzyskać potwierdzenie.

+0

To działa dobrze dla mnie. Wielkie dzięki! –

+3

To jest rozmiar pliku (liczba bajtów w pliku). W zależności od rozmiarów bloków rzeczywistego sprzętu, plik może zużywać więcej miejsca na dysku. Na przykład. plik 600 bajtów na moim dysku twardym używał 4kB na dysku. Tak więc ta odpowiedź jest niepoprawna. – 0xBADF00D

Powiązane problemy