Obtenir la taille du fichier sur le disque

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

Cela donne la taille logique du fichier, pas la taille sur le disque.

Je souhaite obtenir la taille d'un fichier sur le disque en C # (de préférence sans interopérabilité ) comme cela serait signalé par l'Explorateur Windows.

Il doit donner la bonne taille, y compris pour:

  • Un fichier compressé
  • Un fichier clairsemé
  • Un fichier fragmenté
Wernight
la source

Réponses:

50

Cela utilise GetCompressedFileSize, comme ho1 suggéré, ainsi que GetDiskFreeSpace, comme PaulStack l'a suggéré, mais utilise P / Invoke. Je l'ai testé uniquement pour les fichiers compressés et je soupçonne que cela ne fonctionne pas pour les fichiers fragmentés.

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
la source
êtes-vous sûr que c'est correct si (résultat == 0) lance une nouvelle Win32Exception (résultat);
Simon
Le bit 'if (result == 0)' est correct (voir msdn ), mais vous avez raison de dire que j'utilise le mauvais constructeur. Je vais le réparer maintenant.
margnus1
FileInfo.Directory.Rootne semble pas pouvoir gérer n'importe quel type de liens de système de fichiers. Donc, cela ne fonctionne que sur les lettres de lecteur locales classiques sans liens symboliques / liens physiques / points de jonction ou tout ce que NTFS a à offrir.
ygoe
Quelqu'un pourrait-il expliquer étape par étape ce qui a été fait à différentes étapes? Il sera très utile de comprendre comment cela fonctionne réellement, merci.
bapi
5
Ce code nécessite les espaces de noms System.ComponentModelet System.Runtime.InteropServices.
Kenny Evitt
17

Le code ci-dessus ne fonctionne pas correctement sur les systèmes Windows Server 2008 ou 2008 R2 ou Windows 7 et Windows Vista, car la taille du cluster est toujours égale à zéro (GetDiskFreeSpaceW et GetDiskFreeSpace renvoient -1 même avec UAC désactivé.) Voici le code modifié qui fonctionne.

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
la source
Une référence System.Managment est requise pour que ce code fonctionne. Il semble qu'il n'existe aucun moyen standard d'obtenir la taille du cluster avec précision sur Windows (versions 6.x) à l'exception de WMI. : |
Steve Johnson du
1
J'ai écrit mon code sur une machine Vista x64 et je l'ai maintenant testé sur une machine W7 x64 en mode 64 bits et WOW64. Notez que GetDiskFreeSpace est censé renvoyer une valeur différente de zéro en cas de succès .
margnus1
1
Question originale demande pour C #
Shane Courtrille
4
Ce code ne se compile même pas (il manque une parenthèse fermante sur l'utilisation) et la seule ligne est très horrible à des fins d'apprentissage
Mickael V.
1
Ce code a également un problème de compilation lors de la demande .First()car il s'agit d'un IEnumerableet non d'un IEnumerable<T>, si vous souhaitez utiliser le code pour le premier appel.Cast<object>()
yoel halb
5

Selon les forums sociaux MSDN:

La taille sur le disque doit être la somme de la taille des clusters qui stockent le fichier:
long sizeondisk = clustersize * ((filelength + clustersize - 1) / clustersize);
vous devrez plonger dans P / Invoke pour trouver la taille du cluster; GetDiskFreeSpace()le renvoie.

Consultez Comment obtenir la taille sur le disque d'un fichier en C # .

Mais veuillez noter que cela ne fonctionnera pas dans NTFS où la compression est activée.

pile72
la source
2
Je suggère d'utiliser quelque chose comme GetCompressedFileSizeplutôt que filelengthde tenir compte des fichiers compressés et / ou épars.
Hans Olsson
-1

Je pense que ce sera comme ça:

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

Je fais encore des tests pour cela, pour obtenir une confirmation.

bapi
la source
7
Il s'agit de la taille du fichier (nombre d'octets dans le fichier). En fonction de la taille des blocs du matériel réel, un fichier peut consommer plus d'espace disque. Par exemple, un fichier de 600 octets sur mon disque dur utilisait 4 Ko sur le disque. Donc, cette réponse est incorrecte.
0xBADF00D