155 votes

Comment vérifier rapidement si un dossier est vide (.NET) ?

Je dois vérifier si le répertoire sur le disque est vide. Cela signifie qu'il ne contient aucun dossier/fichier. Je sais qu'il existe une méthode simple. Nous obtenons un tableau de FileSystemInfo's et vérifions si le nombre d'éléments est égal à zéro. Quelque chose comme ça :

public static bool CheckFolderEmpty(string path)
{
    if (string.IsNullOrEmpty(path))
    {
        throw new ArgumentNullException("path");
    }

    var folder = new DirectoryInfo(path);
    if (folder.Exists)
    {
        return folder.GetFileSystemInfos().Length == 0;
    }

    throw new DirectoryNotFoundException();
}

Cette approche semble correcte. MAIS ! elle est très, très mauvaise du point de vue des performances. GetFileSystemInfos() est une méthode très difficile. En fait, elle énumère tous les objets du système de fichiers du dossier, récupère toutes leurs propriétés, crée des objets, remplit des tableaux typés, etc. Et tout cela juste pour vérifier la longueur. C'est stupide, n'est-ce pas ?

Je viens de profiler ce code et j'ai déterminé que ~250 appels de cette méthode sont exécutés en ~500ms. C'est très lent et je pense qu'il est possible de le faire beaucoup plus rapidement.

Des suggestions ?

7 votes

Par curiosité, pourquoi voulez-vous vérifier le répertoire 250 fois ?

2 votes

@ya23 Je suppose que l'on voudrait vérifier 250 répertoires différents. Pas un seul 250 fois.

308voto

Thomas Levesque Points 141081

Il existe une nouvelle fonctionnalité dans Directory y DirectoryInfo dans .NET 4 qui leur permet de renvoyer un message de type IEnumerable au lieu d'un tableau, et commencer à retourner les résultats avant de lire tout le contenu du répertoire.


EDIT : en revoyant cette réponse, je réalise que ce code peut être rendu beaucoup plus simple...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}

0 votes

J'aime cette solution, mais peut-on faire en sorte qu'elle ne vérifie que certains types de fichiers ? .Contains("jpg") au lieu de .any() ne semble pas fonctionner.

5 votes

@Dennis, vous pouvez spécifier un motif joker dans l'appel à EnumerateFileSystemEntries ou utiliser .Any(condition) (spécifiez la condition sous la forme d'une expression lambda, ou d'une méthode qui prend un chemin comme paramètre).

0 votes

Le typecast peut être supprimé du premier exemple de code : return !items.GetEnumerator().MoveNext();

33voto

zhe Points 438

Voici la solution extra rapide, que j'ai finalement mise en œuvre. Ici, j'utilise WinAPI et les fonctions FindFirstFile , FindNextFile . Il permet d'éviter l'énumération de tous les éléments du dossier et de l'article. s'arrête juste après avoir détecté le premier objet dans le dossier . Cette approche est ~6( !!) fois plus rapide que celle décrite ci-dessus. 250 appels en 36ms !

private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1);

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct WIN32_FIND_DATA
{
    public uint dwFileAttributes;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftCreationTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastAccessTime;
    public System.Runtime.InteropServices.ComTypes.FILETIME ftLastWriteTime;
    public uint nFileSizeHigh;
    public uint nFileSizeLow;
    public uint dwReserved0;
    public uint dwReserved1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
    public string cFileName;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
    public string cAlternateFileName;
}

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern IntPtr FindFirstFile(string lpFileName, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll", CharSet=CharSet.Auto)]
private static extern bool FindNextFile(IntPtr hFindFile, out WIN32_FIND_DATA lpFindFileData);

[DllImport("kernel32.dll")]
private static extern bool FindClose(IntPtr hFindFile);

public static bool CheckDirectoryEmpty_Fast(string path)
{
    if (string.IsNullOrEmpty(path))
    {
        throw new ArgumentNullException(path);
    }

    if (Directory.Exists(path))
    {
        if (path.EndsWith(Path.DirectorySeparatorChar.ToString()))
            path += "*";
        else
            path += Path.DirectorySeparatorChar + "*";

        WIN32_FIND_DATA findData;
        var findHandle = FindFirstFile(path, out findData);

        if (findHandle != INVALID_HANDLE_VALUE)
        {
            try
            {
                bool empty = true;
                do
                {
                    if (findData.cFileName != "." && findData.cFileName != "..")
                        empty = false;
                } while (empty && FindNextFile(findHandle, out findData));

                return empty;
            }
            finally
            {
                FindClose(findHandle);
            }
        }

        throw new Exception("Failed to get directory first file",
            Marshal.GetExceptionForHR(Marshal.GetHRForLastWin32Error()));
    }
    throw new DirectoryNotFoundException();
}

J'espère qu'il sera utile à quelqu'un dans le futur.

0 votes

Merci de partager votre solution.

3 votes

Vous devez ajouter SetLastError = true à la DllImport para FindFirstFile afin que le Marshal.GetHRForLastWin32Error() pour qu'il fonctionne correctement, comme décrit dans la section Remarques des Documentation MSDN pour GetHRForLastWin32Error() .

0 votes

Je pense que la réponse suivante est un peu meilleure car elle recherche également les fichiers dans les sous-répertoires. stackoverflow.com/questions/724148/

22voto

Marc Gravell Points 482669

Vous pouvez essayer Directory.Exists(path) y Directory.GetFiles(path) - probablement moins de frais généraux (pas d'objets - juste des chaînes, etc.).

0 votes

Comme toujours, vous êtes le plus rapide sur la gâchette ! Tu m'as battu de quelques secondes ! :-)

0 votes

Vous avez été tous les deux plus rapides que moi... mince, mon attention aux détails ;-)

2 votes

Cela ne m'a pas servi, cependant ; première réponse, et la seule sans vote ;-(

19voto

Eoin Campbell Points 22861
private static void test()
{
    System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

    string [] dirs = System.IO.Directory.GetDirectories("C:\\Test\\");
    string[] files = System.IO.Directory.GetFiles("C:\\Test\\");

    if (dirs.Length == 0 && files.Length == 0)
        Console.WriteLine("Empty");
    else
        Console.WriteLine("Not Empty");

    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

Ce test rapide a donné un résultat de 2 millisecondes pour le dossier vide et pour le dossier contenant des sous-dossiers et des fichiers (5 dossiers contenant chacun 5 fichiers).

3 votes

Vous pourriez améliorer cela en retournant directement si 'dirs' est non-vide, sans avoir à obtenir la liste des fichiers.

4 votes

Oui, mais que faire s'il contient des milliers de fichiers ?

3 votes

Vous mesurez également le temps d'écriture dans la console, ce qui n'est pas négligeable.

7voto

Cerebrus Points 18045

Je ne connais pas les statistiques de performance sur ce sujet, mais avez-vous essayé d'utiliser la fonction Directory.GetFiles() Méthode statique ?

Elle renvoie un tableau de chaînes contenant des noms de fichiers (et non des FileInfos) et vous pouvez vérifier la longueur du tableau de la même manière que ci-dessus.

0 votes

Même problème, cela peut être lent s'il y a beaucoup de fichiers... mais c'est probablement plus rapide que GetFileSystemInfos

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X