web-dev-qa-db-fra.com

Quelle est la meilleure façon de calculer la taille d'un répertoire dans .NET?

J'ai écrit la routine suivante pour parcourir manuellement un répertoire et calculer sa taille en C # /. NET:


protected static float CalculateFolderSize(string folder)
{
    float folderSize = 0.0f;
    try
    {
        //Checks if the path is valid or not
        if (!Directory.Exists(folder))
            return folderSize;
        else
        {
            try
            {
                foreach (string file in Directory.GetFiles(folder))
                {
                    if (File.Exists(file))
                    {
                        FileInfo finfo = new FileInfo(file);
                        folderSize += finfo.Length;
                    }
                }

                foreach (string dir in Directory.GetDirectories(folder))
                    folderSize += CalculateFolderSize(dir);
            }
            catch (NotSupportedException e)
            {
                Console.WriteLine("Unable to calculate folder size: {0}", e.Message);
            }
        }
    }
    catch (UnauthorizedAccessException e)
    {
        Console.WriteLine("Unable to calculate folder size: {0}", e.Message);
    }
    return folderSize;
}

J'ai une application qui exécute cette routine à plusieurs reprises pour un grand nombre de dossiers. Je me demande s'il existe un moyen plus efficace de calculer la taille d'un dossier avec .NET? Je n'ai rien vu de spécifique dans le cadre. Dois-je utiliser P/Invoke et une API Win32? Quelle est la manière la plus efficace de calculer la taille d'un dossier dans .NET?

65
Steve Wranovsky

Je ne crois pas qu'il existe une API Win32 pour calculer l'espace consommé par un répertoire, bien que je sois corrigé à ce sujet. S'il y en avait, je suppose qu'Explorer l'utiliserait. Si vous obtenez les propriétés d'un grand répertoire dans l'Explorateur, le temps nécessaire pour vous donner la taille du dossier est proportionnel au nombre de fichiers/sous-répertoires qu'il contient.

Votre routine semble assez soignée et simple. Gardez à l'esprit que vous calculez la somme des longueurs de fichier, pas l'espace réel consommé sur le disque. L'espace consommé par l'espace gaspillé à la fin des clusters, des flux de fichiers, etc., est ignoré.

25
Mike Thompson

Non, cela ressemble à la méthode recommandée pour calculer la taille du répertoire, la méthode appropriée incluse ci-dessous:

public static long DirSize(DirectoryInfo d) 
{    
    long size = 0;    
    // Add file sizes.
    FileInfo[] fis = d.GetFiles();
    foreach (FileInfo fi in fis) 
    {      
        size += fi.Length;    
    }
    // Add subdirectory sizes.
    DirectoryInfo[] dis = d.GetDirectories();
    foreach (DirectoryInfo di in dis) 
    {
        size += DirSize(di);   
    }
    return size;  
}

Vous appelleriez avec la racine comme:

Console.WriteLine("The size is {0} bytes.", DirSize(new DirectoryInfo(targetFolder));

... où targetFolder est la taille du dossier à calculer.

54
hao

La meilleure et la ligne la plus courte pourrait suivre

  long length = Directory.GetFiles(directoryPath,"*",SearchOption.AllDirectories).Sum(t => (new FileInfo(t).Length));
21
Trikaldarshi

La vraie question est, pour quoi avez-vous l'intention d'utiliser la taille pour ?

Votre premier problème est qu'il y a au moins quatre définitions de "taille de fichier":

  • L'offset "fin de fichier", qui est le nombre d'octets que vous devez ignorer pour passer du début à la fin du fichier.
    En d'autres termes, c'est le nombre d'octets logiquement dans le fichier (du point de vue de l'utilisation).

  • La "longueur de données valide", qui est égale au décalage du premier octet qui n'est pas réellement stocké .
    Ceci est toujours inférieur ou égal à la "fin du fichier" et est un multiple de la taille du cluster.
    Par exemple, un fichier de 1 Go peut avoir une longueur de données valide de 1 Mo. Si vous demandez à Windows de lire les 8 premiers Mo, il lira les 1 premiers Mo et prétendra que le reste des données était là, en le renvoyant sous forme de zéros.

  • La "taille allouée" d'un fichier. Ceci est toujours supérieur ou égal à la "fin de fichier".
    Il s'agit du nombre de clusters que le système d'exploitation a alloué pour le fichier, multiplié par la taille du cluster.
    Contrairement au cas où la "fin de fichier" est supérieure à la "longueur de données valide", les octets excédentaires sont pas considérés comme faisant partie des données du fichier, donc le système d'exploitation pas remplissez un tampon avec des zéros si vous essayez de lire dans la région allouée au-delà de la fin du fichier.

  • La "taille compressée" d'un fichier, qui n'est valable que pour les fichiers compressés (et clairsemés?).
    Il est égal à la taille d'un cluster, multipliée par le nombre de clusters sur le volume qui sont réellement alloués à ce fichier.
    Pour les fichiers non compressés et non clairsemés, il n'y a pas de notion de "taille compressée"; vous utiliseriez la "taille allouée" à la place.

Votre deuxième problème est qu'un "fichier" comme C:\Foo peut en fait avoir plusieurs flux de données.
Ce nom fait simplement référence au flux par défaut . Un fichier peut avoir des flux alternatifs , comme C:\Foo:Bar, dont la taille n'est même pas affichée dans l'Explorateur!

Votre troisième problème est qu'un "fichier" peut avoir plusieurs noms ( "liens durs").
Par exemple, C:\Windows\notepad.exe et C:\Windows\System32\notepad.exe sont deux noms pour le même fichier . Tout nom peut être utilisé pour ouvrir tout flux du fichier.

Votre quatrième problème est qu'un "fichier" (ou répertoire) pourrait en fait ne pas être même un fichier (ou répertoire):
Il peut s'agir d'un lien logiciel ("lien symbolique" ou "point d'analyse") vers un autre fichier (ou répertoire) .
Cet autre fichier peut même ne pas être sur le même lecteur. Cela pourrait même pointer vers quelque chose sur le réseau, ou cela pourrait même être récursif! La taille doit-elle être infinie si elle est récursive?

Votre cinquième est qu'il existe des pilotes de "filtre" qui donnent à certains fichiers ou répertoires l'apparence comme les fichiers ou répertoires réels, même s'ils ne le sont pas. Par exemple, les fichiers image WIM de Microsoft (qui sont compressés) peuvent être "montés" sur un dossier à l'aide d'un outil appelé ImageX, et ceux ne ressemblent pas analyser des points ou des liens. Ils ressemblent à des répertoires - sauf qu'ils ne sont pas réellement des répertoires, et la notion de "taille" n'a pas vraiment de sens pour eux.

Votre sixième problème est que chaque fichier nécessite des métadonnées.
Par exemple, avoir 10 noms pour le même fichier nécessite plus de métadonnées, ce qui nécessite de l'espace. Si les noms de fichiers sont courts, avoir 10 noms peut être aussi bon marché qu'avoir 1 nom - et s'ils sont longs, alors avoir plusieurs noms peut utiliser plus d'espace disque pour les métadonnées . (Même histoire avec plusieurs flux, etc.)
Les comptez-vous aussi?

15
Mehrdad
public static long DirSize(DirectoryInfo dir)
{
    return dir.GetFiles().Sum(fi => fi.Length) +
           dir.GetDirectories().Sum(di => DirSize(di));
}
13
Grozz
var size = new DirectoryInfo("E:\\").GetDirectorySize();

et voici le code derrière cette méthode d'extension

public static long GetDirectorySize(this System.IO.DirectoryInfo directoryInfo, bool recursive = true)
{
    var startDirectorySize = default(long);
    if (directoryInfo == null || !directoryInfo.Exists)
        return startDirectorySize; //Return 0 while Directory does not exist.

    //Add size of files in the Current Directory to main size.
    foreach (var fileInfo in directoryInfo.GetFiles())
        System.Threading.Interlocked.Add(ref startDirectorySize, fileInfo.Length);

    if (recursive) //Loop on Sub Direcotries in the Current Directory and Calculate it's files size.
        System.Threading.Tasks.Parallel.ForEach(directoryInfo.GetDirectories(), (subDirectory) =>
    System.Threading.Interlocked.Add(ref startDirectorySize, GetDirectorySize(subDirectory, recursive)));

    return startDirectorySize;  //Return full Size of this Directory.
}
5
Ahmed Sabry

Plus vite! Ajoutez la référence COM "Objet hôte de script Windows ..."

public double GetWSHFolderSize(string Fldr)
    {
        //Reference "Windows Script Host Object Model" on the COM tab.
        IWshRuntimeLibrary.FileSystemObject FSO = new     IWshRuntimeLibrary.FileSystemObject();
        double FldrSize = (double)FSO.GetFolder(Fldr).Size;
        Marshal.FinalReleaseComObject(FSO);
        return FldrSize;
    }
private void button1_Click(object sender, EventArgs e)
        {
            string folderPath = @"C:\Windows";
        Stopwatch sWatch = new Stopwatch();

        sWatch.Start();
        double sizeOfDir = GetWSHFolderSize(folderPath);
        sWatch.Stop();
        MessageBox.Show("Directory size in Bytes : " + sizeOfDir + ", Time: " + sWatch.ElapsedMilliseconds.ToString());
          }
5
Alex

Il semble que la méthode suivante exécute votre tâche plus rapidement que la fonction récursive:

long size = 0;
DirectoryInfo dir = new DirectoryInfo(folder);
foreach (FileInfo fi in dir.GetFiles("*.*", SearchOption.AllDirectories))
{
   size += fi.Length;
}

Un simple test d'application console montre que cette boucle additionne les fichiers plus rapidement que la fonction récursive et fournit le même résultat. Et vous voudrez probablement utiliser des méthodes LINQ (comme Sum ()) pour raccourcir ce code.

4
Konstantin K.

cette solution fonctionne très bien. il rassemble tous les sous-dossiers:

Directory.GetFiles(@"MainFolderPath", "*", SearchOption.AllDirectories).Sum(t => (new FileInfo(t).Length));
4
Shai Segev

J'ai étendu la réponse de @ Hao en utilisant le même principe de comptage mais en prenant en charge un retour de données plus riche, de sorte que vous récupérez la taille, la taille récursive, le nombre de répertoires et le nombre de répertoires récursifs, N niveaux de profondeur.

public class DiskSizeUtil
{
    /// <summary>
    /// Calculate disk space usage under <paramref name="root"/>.  If <paramref name="levels"/> is provided, 
    /// then return subdirectory disk usages as well, up to <paramref name="levels"/> levels deep.
    /// If levels is not provided or is 0, return a list with a single element representing the
    /// directory specified by <paramref name="root"/>.
    /// </summary>
    /// <returns></returns>
    public static FolderSizeInfo GetDirectorySize(DirectoryInfo root, int levels = 0)
    {
        var currentDirectory = new FolderSizeInfo();

        // Add file sizes.
        FileInfo[] fis = root.GetFiles();
        currentDirectory.Size = 0;
        foreach (FileInfo fi in fis)
        {
            currentDirectory.Size += fi.Length;
        }

        // Add subdirectory sizes.
        DirectoryInfo[] dis = root.GetDirectories();

        currentDirectory.Path = root;
        currentDirectory.SizeWithChildren = currentDirectory.Size;
        currentDirectory.DirectoryCount = dis.Length;
        currentDirectory.DirectoryCountWithChildren = dis.Length;
        currentDirectory.FileCount = fis.Length;
        currentDirectory.FileCountWithChildren = fis.Length;

        if (levels >= 0)
            currentDirectory.Children = new List<FolderSizeInfo>();

        foreach (DirectoryInfo di in dis)
        {
            var dd = GetDirectorySize(di, levels - 1);
            if (levels >= 0)
                currentDirectory.Children.Add(dd);

            currentDirectory.SizeWithChildren += dd.SizeWithChildren;
            currentDirectory.DirectoryCountWithChildren += dd.DirectoryCountWithChildren;
            currentDirectory.FileCountWithChildren += dd.FileCountWithChildren;
        }

        return currentDirectory;
    }

    public class FolderSizeInfo
    {
        public DirectoryInfo Path { get; set; }
        public long SizeWithChildren { get; set; }
        public long Size { get; set; }
        public int DirectoryCount { get; set; }
        public int DirectoryCountWithChildren { get; set; }
        public int FileCount { get; set; }
        public int FileCountWithChildren { get; set; }
        public List<FolderSizeInfo> Children { get; set; }
    }
}
4
Scott Stafford

J'ai joué avec VS2008 et LINQ jusqu'à récemment et cette méthode compacte et courte fonctionne très bien pour moi (l'exemple est dans VB.NET; nécessite LINQ/.NET FW 3.5+ bien sûr):

Dim size As Int64 = (From strFile In My.Computer.FileSystem.GetFiles(strFolder, _
              FileIO.SearchOption.SearchAllSubDirectories) _
              Select New System.IO.FileInfo(strFile).Length).Sum()

Son court, il recherche les sous-répertoires et est simple à comprendre si vous connaissez la syntaxe LINQ. Vous pouvez même spécifier des caractères génériques pour rechercher des fichiers spécifiques à l'aide du troisième paramètre de la fonction .GetFiles.

Je ne suis pas un expert en C # mais vous pouvez ajouter l'espace de noms My sur C # de cette façon .

Je pense que cette façon d'obtenir une taille de dossier n'est pas seulement plus courte et plus moderne que la manière décrite sur Hao link , elle utilise essentiellement la même méthode de boucle de FileInfo décrite à la fin.

4
Rodolfo G.

C'est la meilleure façon de calculer la taille d'un répertoire. Seule une autre façon utiliserait la récursivité mais serait un peu plus facile à utiliser et n'est pas aussi flexible.

float folderSize = 0.0f;
FileInfo[] files = Directory.GetFiles(folder, "*", SearchOption.AllDirectories);
foreach(FileInfo file in files) folderSize += file.Length;
4
Samuel
Directory.GetFiles(@"C:\Users\AliBayat","*.*",SearchOption.AllDirectories)
.Select (d => new FileInfo(d))
.Select (d => new { Directory = d.DirectoryName,FileSize = d.Length} )
.ToLookup (d => d.Directory )
.Select (d => new { Directory = d.Key,TotalSizeInMB =Math.Round(d.Select (x =>x.FileSize)
.Sum () /Math.Pow(1024.0,2),2)})
.OrderByDescending (d => d.TotalSizeInMB).ToList();

L'appel de GetFiles avec SearchOption.AllDirectories Renvoie le nom complet de tous les fichiers dans tous les subdirectories du répertoire spécifié. Le système d'exploitation représente la taille des fichiers en octets. Vous pouvez récupérer la taille du fichier à partir de sa propriété Length. Le diviser par 1024 élevé à la puissance de 2 vous donne la taille du fichier en mégaoctets. Étant donné qu'un répertoire/dossier peut contenir de nombreux fichiers, d.Select(x => x.FileSize) renvoie une collection de tailles de fichiers mesurées en mégaoctets. Le dernier appel à Sum() recherche la taille totale des fichiers dans le répertoire spécifié.

3
Ali Bayat
public static long GetDirSize(string path)
{
    try
    {
        return Directory.EnumerateFiles(path).Sum(x => new FileInfo(x).Length)  
            +
               Directory.EnumerateDirectories(path).Sum(x => GetDirSize(x));
    }
    catch
    {
        return 0L;
    }
}
3
Raz Megrelidze

Pour améliorer les performances, vous pouvez utiliser la bibliothèque parallèle de tâches (TPL). Voici un bon exemple: Calcul de la taille du fichier répertoire - comment le rendre plus rapide?

Je ne l'ai pas testé, mais l'auteur dit que c'est 3 fois plus rapide qu'une méthode non multithread ...

2
Bidou

En ce qui concerne le meilleur algorithme, vous avez probablement raison. Je vous recommande de démêler la fonction récursive et d'utiliser votre propre pile (rappelez-vous qu'un débordement de pile est la fin du monde dans une application .Net 2.0+, l'exception ne peut pas être interceptée IIRC).

La chose la plus importante est que si vous l'utilisez sous n'importe quelle forme d'interface utilisateur, placez-le sur un thread de travail qui signale le thread d'interface utilisateur avec des mises à jour.

2

Cette application de ligne de commande .NET core calcule ici la taille des répertoires pour un chemin donné:

https://github.com/garethrbrown/folder-size

La méthode clé est celle-ci qui inspecte récursivement les sous-répertoires pour trouver une taille totale.

private static long DirectorySize(SortDirection sortDirection, DirectoryInfo directoryInfo, DirectoryData directoryData)
{
        long directorySizeBytes = 0;

        // Add file sizes for current directory

        FileInfo[] fileInfos = directoryInfo.GetFiles();

        foreach (FileInfo fileInfo in fileInfos)
        {
            directorySizeBytes += fileInfo.Length;
        }

        directoryData.Name = directoryInfo.Name;

        directoryData.SizeBytes += directorySizeBytes;

        // Recursively add subdirectory sizes

        DirectoryInfo[] subDirectories = directoryInfo.GetDirectories();

        foreach (DirectoryInfo di in subDirectories)
        {
            var subDirectoryData = new DirectoryData(sortDirection);

            directoryData.DirectoryDatas.Add(subDirectoryData);

            directorySizeBytes += DirectorySize(sortDirection, di, subDirectoryData);
        }

        directoryData.SizeBytes = directorySizeBytes;

        return directorySizeBytes;
    }
}
1
gb2d

Le moyen le plus rapide que j'ai trouvé est d'utiliser EnumerateFiles avec SearchOption.AllDirectories. Cette méthode permet également de mettre à jour l'interface utilisateur tout en parcourant les fichiers et en comptant la taille. Les noms de chemin long ne posent aucun problème car FileInfo ou DirectoryInfo ne sont pas tentés d'être créés pour le nom de chemin long. Lors de l'énumération des fichiers, même si le nom de fichier est long, le FileInfo renvoyé par les EnumerateFiles ne pose pas de problème tant que le nom du répertoire de départ n'est pas trop long. Il y a toujours un problème avec UnauthorizedAccess.

    private void DirectoryCountEnumTest(string sourceDirName)
    {
        // Get the subdirectories for the specified directory.
        long dataSize = 0;
        long fileCount = 0;
        string prevText = richTextBox1.Text;

        if (Directory.Exists(sourceDirName))
        {
            DirectoryInfo dir = new DirectoryInfo(sourceDirName);
            foreach (FileInfo file in dir.EnumerateFiles("*", SearchOption.AllDirectories))
            {
                fileCount++;
                try
                {
                    dataSize += file.Length;
                    richTextBox1.Text = prevText + ("\nCounting size: " + dataSize.ToString());
                }
                catch (Exception e)
                {
                    richTextBox1.AppendText("\n" + e.Message);
                }
            }
            richTextBox1.AppendText("\n files:" + fileCount.ToString());
        }
    }
1
ladybug

Je sais que ce n'est pas une solution .net mais ici ça vient quand même. Peut-être que cela est pratique pour les personnes qui ont Windows 10 et qui souhaitent une solution plus rapide. Par exemple, si vous exécutez cette commande avec votre invite de commande ou en appuyant sur winKey + R:

bash -c "du -sh /mnt/c/Users/; sleep 5"    

Le sleep 5 Est ainsi vous avez le temps de voir les résultats et la fenêtre ne se ferme pas

Dans mon ordinateur qui affiche:

enter image description here

Notez à la fin comment il affiche 85G (85 Gigabytes). C'est un souper rapide par rapport à le faire avec .Net. Si vous voulez voir la taille plus précisément, supprimez le h qui signifie lisible par l'homme.

Il suffit donc de faire quelque chose comme Processes.Start("bash",... arguments) Ce n'est pas le code exact mais vous avez l'idée.

0
Tono Nam