Obtenha o tamanho do arquivo no disco

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

Isso fornece o tamanho lógico do arquivo, não o tamanho do disco.

Desejo obter o tamanho de um arquivo no disco em C # (de preferência sem interoperabilidade ), como seria relatado pelo Windows Explorer.

Deve ter o tamanho correto, incluindo:

  • Um arquivo compactado
  • Um arquivo esparso
  • Um arquivo fragmentado
Wernight
fonte

Respostas:

50

Este usa GetCompressedFileSize, como sugerido por ho1, bem como GetDiskFreeSpace, como sugeriu PaulStack, mas usa P / Invoke. Testei-o apenas para arquivos compactados e suspeito que não funcione com arquivos 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
fonte
tem certeza de que isso está correto se (resultado == 0) lançar uma nova Win32Exception (resultado);
Simon
O bit 'if (result == 0)' está correto (consulte o msdn ), mas você está certo ao dizer que estou usando o construtor errado. Eu vou consertar isso agora.
margnus1 01 de
FileInfo.Directory.Rootnão parece que possa lidar com qualquer tipo de link de sistema de arquivos. Portanto, ele só funciona em letras de unidade locais clássicas sem links simbólicos / links físicos / pontos de junção ou o que o NTFS tem a oferecer.
ygoe
Alguém poderia dar uma explicação passo a passo, o que foi feito nas diferentes etapas? Será muito útil entender como realmente funciona. Obrigado.
bapi
5
Este código requer os namespaces System.ComponentModele System.Runtime.InteropServices.
Kenny Evitt
17

O código acima não funciona corretamente no Windows Server 2008 ou 2008 R2 ou nos sistemas baseados no Windows 7 e Windows Vista, pois o tamanho do cluster é sempre zero (GetDiskFreeSpaceW e GetDiskFreeSpace retornam -1 mesmo com o UAC desativado.) Aqui está o 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
fonte
A referência System.Managment é necessária para que este código funcione. Parece que não existe uma maneira padrão de obter o tamanho do cluster com precisão no Windows (versões 6.x), exceto WMI. : |
Steve Johnson
1
Eu escrevi meu código em uma máquina Vista x64 e agora testei em uma máquina W7 x64 no modo de 64 bits e WOW64. Observe que GetDiskFreeSpace deve retornar diferente de zero em caso de sucesso .
margnus1 01 de
1
A pergunta original pede para C #
Shane Courtrille
4
Este código nem mesmo compila (falta um parêntese de fechamento no uso) e o liner é muito ruim para fins de aprendizagem
Mickael V.
1
Este código também tem um problema de compilação ao solicitar .First(), pois é um IEnumerablee não um IEnumerable<T>, se você quiser usar o código, primeiro chame.Cast<object>()
yoel halb
5

De acordo com os fóruns sociais do MSDN:

O tamanho no disco deve ser a soma do tamanho dos clusters que armazenam o arquivo:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
você precisará mergulhar em P / Invoke para encontrar o tamanho do cluster; GetDiskFreeSpace()retorna.

Veja Como obter o tamanho no disco de um arquivo em C # .

Mas observe o ponto que isso não funcionará em NTFS onde a compressão está ativada.

pilha 72
fonte
2
Eu sugiro usar algo como em GetCompressedFileSizevez de filelengthcontabilizar arquivos compactados e / ou esparsos.
Hans Olsson de
-1

Acho que vai ser assim:

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

Ainda estou fazendo alguns testes para obter uma confirmação.

bapi
fonte
7
Este é o tamanho do arquivo (número de bytes dentro do arquivo). Dependendo dos tamanhos de bloco do hardware real, um arquivo pode consumir mais espaço em disco. Por exemplo, um arquivo de 600 bytes no meu HDD usou 4kB no disco. Portanto, esta resposta está incorreta.
0xBADF00D