Ottieni la dimensione del file su disco


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

Ciò fornisce la dimensione logica del file, non la dimensione sul disco.

Desidero ottenere la dimensione di un file sul disco in C # (preferibilmente senza interoperabilità ) come sarebbe riportato da Windows Explorer.

Dovrebbe fornire la dimensione corretta, anche per:

  • Un file compresso
  • Un file scarso
  • Un file frammentato

Risposte:


50

Questo utilizza GetCompressedFileSize, come suggerito da ho1, così come GetDiskFreeSpace, come suggerito da PaulStack, tuttavia utilizza P / Invoke. L'ho testato solo per i file compressi e sospetto che non funzioni per i file frammentati.

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

sei sicuro che sia corretto se (risultato == 0) lancia una nuova Win32Exception (risultato);
Simon

Il bit "if (result == 0)" è corretto (vedi msdn ), ma hai ragione che sto usando il costruttore sbagliato. Lo aggiusterò ora.
margnus1

FileInfo.Directory.Rootnon sembra che possa gestire qualsiasi tipo di collegamento al filesystem. Quindi funziona solo con le classiche lettere di unità locali senza collegamenti simbolici / collegamenti fisici / punti di giunzione o qualsiasi altra cosa NTFS ha da offrire.
ygoe

Qualcuno potrebbe fornire una spiegazione passo passo, cosa è stato fatto in diverse fasi? Sarà molto utile capire come funziona effettivamente. Grazie.
bapi

5
Questo codice richiede gli spazi dei nomi System.ComponentModele System.Runtime.InteropServices.
Kenny Evitt

17

Il codice sopra non funziona correttamente su sistemi basati su Windows Server 2008 o 2008 R2 o Windows 7 e Windows Vista poiché la dimensione del cluster è sempre zero (GetDiskFreeSpaceW e GetDiskFreeSpace restituiscono -1 anche con UAC disabilitato). Ecco il codice modificato che funziona.

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

Il riferimento System.Managment è necessario affinché questo codice funzioni. Sembra che non esista un modo standard per ottenere con precisione la dimensione del cluster su Windows (versioni 6.x) eccetto WMI. : |
Steve Johnson,

1
Ho scritto il mio codice su una macchina Vista x64 e ora l'ho testato su una macchina W7 x64 in modalità 64 bit e WOW64. Nota che GetDiskFreeSpace dovrebbe restituire diverso da zero in caso di successo .
margnus1

1
La domanda originale chiede C #
Shane Courtrille

4
Questo codice non si compila nemmeno (una parentesi di chiusura manca nell'uso) e l'unica riga è molto orribile per scopi di apprendimento
Mickael V.

1
Questo codice ha anche un problema di compilazione durante la richiesta .First()in quanto è an IEnumerablee non an IEnumerable<T>, se si desidera utilizzare il codice prima chiamata.Cast<object>()
yoel halb

5

Secondo i forum sociali di MSDN:

La dimensione su disco dovrebbe essere la somma della dimensione dei cluster che memorizzano il file:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
dovrai immergerti in P / Invoke per trovare la dimensione del cluster; GetDiskFreeSpace()lo restituisce.

Vedere Come ottenere la dimensione su disco di un file in C # .

Ma tieni presente che questo non funzionerà in NTFS dove la compressione è attiva.


2
Suggerisco di utilizzare qualcosa di simile GetCompressedFileSizepiuttosto che filelengthper tenere conto di file compressi e / o sparsi.
Hans Olsson,

-1

Penso che sarà così:

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

Sto ancora facendo dei test per questo, per avere una conferma.


7
Questa è la dimensione del file (numero di byte all'interno del file). A seconda delle dimensioni dei blocchi dell'hardware effettivo, un file potrebbe occupare più spazio su disco. Ad esempio, un file di 600 byte sul mio HDD ha utilizzato 4kB su disco. Quindi questa risposta non è corretta.
0xBADF00D
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.