Obtener el tamaño del archivo en el disco

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

Esto da el tamaño lógico del archivo, no el tamaño del disco.

Deseo obtener el tamaño de un archivo en el disco en C # (preferiblemente sin interoperabilidad ) como lo informa el Explorador de Windows.

Debe tener el tamaño correcto, incluso para:

  • Un archivo comprimido
  • Un archivo escaso
  • Un archivo fragmentado
Wernight
fuente

Respuestas:

50

Esto usa GetCompressedFileSize, como sugirió ho1, así como GetDiskFreeSpace, como sugirió PaulStack, sin embargo, usa P / Invoke. Lo he probado solo para archivos comprimidos y sospecho que no funciona para archivos fragmentados.

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
fuente
¿Está seguro de que esto es correcto si (resultado == 0) lanza una nueva Win32Exception (resultado)?
Simon
El bit 'if (result == 0)' es correcto (ver msdn ), pero tiene razón en que estoy usando el constructor incorrecto. Lo arreglaré ahora.
margnus1
FileInfo.Directory.Rootno parece que pueda manejar ningún tipo de enlaces del sistema de archivos. Por lo tanto, solo funciona en letras de unidades locales clásicas sin enlaces simbólicos / enlaces físicos / puntos de unión o lo que sea que NTFS tenga para ofrecer.
ygoe
¿Alguien podría dar una explicación paso a paso, qué se ha hecho en los diferentes pasos? Será muy útil comprender cómo funciona realmente. Gracias.
bapi
5
Este código requiere los espacios de nombres System.ComponentModely System.Runtime.InteropServices.
Kenny Evitt
17

El código anterior no funciona correctamente en sistemas basados en Windows Server 2008 o 2008 R2 o Windows 7 y Windows Vista, ya que el tamaño del clúster es siempre cero (GetDiskFreeSpaceW y GetDiskFreeSpace devuelven -1 incluso con UAC desactivado). Aquí está el código modificado que funciona.

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
Steve Johnson
fuente
Se requiere una referencia de System.Managment para que este código funcione. Parece que no hay una forma estándar de obtener el tamaño del clúster con precisión en Windows (versiones 6.x) excepto WMI. : |
Steve Johnson
1
Escribí mi código en una máquina Vista x64 y ahora lo probé en una máquina W7 x64 en modo de 64 bits y WOW64. Tenga en cuenta que se supone que GetDiskFreeSpace devuelve un valor distinto de cero en caso de éxito .
margnus1
1
La pregunta original pide C #
Shane Courtrille
4
Este código ni siquiera se compila (falta un paréntesis de cierre en el uso) y la única línea es muy mala para fines de aprendizaje
Mickael V.
1
Este código también tiene un problema de compilación al solicitarlo, .First()ya que es un IEnumerabley no un IEnumerable<T>, si desea usar el código en la primera llamada.Cast<object>()
yoel halb
5

Según los foros sociales de MSDN:

El tamaño en el disco debe ser la suma del tamaño de los clústeres que almacenan el archivo:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
deberá sumergirse en P / Invoke para encontrar el tamaño del clúster; GetDiskFreeSpace()lo devuelve.

Consulte Cómo obtener el tamaño en disco de un archivo en C # .

Pero tenga en cuenta el hecho de que esto no funcionará en NTFS cuando la compresión esté activada.

stack72
fuente
2
Sugiero usar algo como en GetCompressedFileSizelugar de filelengthtener en cuenta los archivos comprimidos y / o dispersos.
Hans Olsson
-1

Creo que será así:

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

Todavía estoy haciendo algunas pruebas para esto, para obtener una confirmación.

bapi
fuente
7
Este es el tamaño del archivo (número de bytes dentro del archivo). Dependiendo de los tamaños de bloque del hardware real, un archivo puede consumir más espacio en disco. Por ejemplo, un archivo de 600 bytes en mi HDD usó 4kB en el disco. Entonces esta respuesta es incorrecta.
0xBADF00D