Wie kann ich schnell überprüfen, ob der Ordner leer ist (.NET)?

140

Ich muss überprüfen, ob das Verzeichnis auf der Festplatte leer ist. Dies bedeutet, dass es keine Ordner / Dateien enthält. Ich weiß, dass es eine einfache Methode gibt. Wir erhalten ein Array von FileSystemInfos und prüfen, ob die Anzahl der Elemente gleich Null ist. Sowas in der Art:

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();
}

Dieser Ansatz scheint in Ordnung zu sein. ABER!! Aus Sicht der Leistung ist es sehr, sehr schlecht. GetFileSystemInfos () ist eine sehr schwierige Methode. Tatsächlich werden alle Dateisystemobjekte des Ordners aufgelistet, alle ihre Eigenschaften abgerufen, Objekte erstellt, typisierte Arrays gefüllt usw. Und dies alles nur, um einfach die Länge zu überprüfen. Das ist doch blöd, oder?

Ich habe gerade einen solchen Code profiliert und festgestellt, dass ~ 250 Aufrufe einer solchen Methode in ~ 500 ms ausgeführt werden. Das ist sehr langsam und ich glaube, dass es möglich ist, es viel schneller zu machen.

Irgendwelche Vorschläge?

zhe
quelle
7
Warum möchten Sie aus Neugier das Verzeichnis 250 Mal überprüfen?
ya23
2
@ ya23 Ich nehme an, man möchte 250 verschiedene Verzeichnisse überprüfen. 250 Mal kein einziger.
Mathieu Pagé

Antworten:

282

In Directoryund DirectoryInfoin .NET 4 gibt es eine neue Funktion , mit der sie IEnumerableein Array anstelle eines Arrays zurückgeben und Ergebnisse zurückgeben können, bevor sie den gesamten Verzeichnisinhalt lesen.

public bool IsDirectoryEmpty(string path)
{
    IEnumerable<string> items = Directory.EnumerateFileSystemEntries(path);
    using (IEnumerator<string> en = items.GetEnumerator())
    {
        return !en.MoveNext();
    }
}

EDIT: Als ich diese Antwort wieder sehe, ist mir klar, dass dieser Code viel einfacher gemacht werden kann ...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}
Thomas Levesque
quelle
Ich mag diese Lösung. Kann sie nur auf bestimmte Dateitypen überprüft werden? .Contains ("jpg") anstelle von .any () schien nicht zu funktionieren
Dennis
5
@Dennis können Sie beim Aufruf EnumerateFileSystemEntriesoder bei der Verwendung ein Platzhaltermuster .Any(condition)angeben (verwenden Sie die Bedingung als Lambda-Ausdruck oder als Methode, die einen Pfad als Parameter verwendet).
Thomas Levesque
Der Typecast kann aus dem ersten Codebeispiel entfernt werden:return !items.GetEnumerator().MoveNext();
Gary
1
@gary, wenn Sie das tun, wird der Enumerator nicht entsorgt, so dass das Verzeichnis gesperrt wird, bis der Enumerator durch Müll gesammelt wird.
Thomas Levesque
Dies scheint für Verzeichnisse mit Dateien gut zu funktionieren, aber wenn das Verzeichnis andere Direktoren enthält, wird es wieder angezeigt und sagt, dass es leer ist.
Kairan
32

Hier ist die extra schnelle Lösung, die ich endlich implementiert habe. Hier verwende ich WinAPI und Funktionen FindFirstFile , FindNextFile . Es ermöglicht die Aufzählung aller Elemente im Ordner und stoppt direkt nach dem Erkennen des ersten Objekts im Ordner . Dieser Ansatz ist ~ 6 (!!) Mal schneller als oben beschrieben. 250 Anrufe in 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();
}

Ich hoffe, dass es in Zukunft für jemanden nützlich sein wird.

zhe
quelle
Vielen Dank, dass Sie Ihre Lösung geteilt haben.
Greg
3
Sie müssen SetLastError = truedas DllImportfor hinzufügen , FindFirstFiledamit der Marshal.GetHRForLastWin32Error()Aufruf ordnungsgemäß funktioniert, wie im Abschnitt "Bemerkungen" des MSDN-Dokuments für GetHRForLastWin32Error () beschrieben .
Joel V. Earnest-DeYoung
Ich denke, die folgende Antwort ist wenig besser, da sie auch nach den Dateien in Unterverzeichnissen sucht. Stackoverflow.com/questions/724148/…
Mayank
21

Sie könnten versuchen Directory.Exists(path)und Directory.GetFiles(path)- wahrscheinlich weniger Aufwand (keine Objekte - nur Zeichenfolgen usw.).

Marc Gravell
quelle
Wie immer sind Sie am schnellsten! Schlage mich dort um ein paar Sekunden! :-)
Cerebrus
Sie waren beide schneller als ich ... verdammt meine Liebe zum Detail ;-)
Eoin Campbell
2
Hat mir aber nichts Gutes getan; erste Antwort und die einzige ohne Abstimmung ;-(
Marc Gravell
Unfixed ... jemand hat eine Axt zum Schleifen, denkt nach
Marc Gravell
1
Ich glaube nicht, dass GetFiles eine Liste von Verzeichnissen erhalten wird, daher scheint es eine gute Idee zu sein, auch
GetDirectories zu
18
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);
}

Dieser Schnelltest kam in 2 Millisekunden für den Ordner zurück, wenn er leer war und Unterordner und Dateien enthielt (5 Ordner mit jeweils 5 Dateien).

Eoin Campbell
quelle
3
Sie können dies verbessern, indem Sie zurückkehren, wenn 'dirs' sofort nicht leer ist, ohne die Liste der Dateien abrufen zu müssen.
Samjudson
3
Ja, aber was ist, wenn sich Tausende von Dateien darin befinden?
Thomas Levesque
3
Sie messen auch die Zeit zum Schreiben auf die Konsole, was nicht zu vernachlässigen ist.
Ctusch
11

Ich benutze dies für Ordner und Dateien (weiß nicht, ob es optimal ist)

    if(Directory.GetFileSystemEntries(path).Length == 0)
Jmu
quelle
8

Wenn es Ihnen nichts ausmacht, reines C # zu verlassen und WinApi- Aufrufe auszuführen , sollten Sie die Funktion PathIsDirectoryEmpty () in Betracht ziehen . Laut MSDN ist die Funktion:

Gibt TRUE zurück, wenn pszPath ein leeres Verzeichnis ist. Gibt FALSE zurück, wenn pszPath kein Verzeichnis ist oder wenn es mindestens eine andere Datei als "" enthält. oder "..".

Das scheint eine Funktion zu sein, die genau das tut, was Sie wollen, daher ist sie wahrscheinlich gut für diese Aufgabe optimiert (obwohl ich das nicht getestet habe).

Um es von C # aus aufzurufen , sollte Ihnen die Website pinvoke.net helfen. (Leider wird diese bestimmte Funktion noch nicht beschrieben, aber Sie sollten in der Lage sein, einige Funktionen mit ähnlichen Argumenten und Rückgabetypen dort zu finden und sie als Grundlage für Ihren Aufruf zu verwenden. Wenn Sie erneut in die MSDN schauen, heißt es, dass Die zu importierende DLL ist shlwapi.dll)

akavel
quelle
Großartige Idee. Ich wusste nichts über diese Funktion. Ich werde versuchen, die Leistung mit meinem Ansatz zu vergleichen, den ich oben beschrieben habe. Wenn es schneller gehen würde, werde ich es in meinem Code wiederverwenden. Vielen Dank.
zhe
4
Ein Hinweis für diejenigen, die diesen Weg gehen wollen. Es scheint, dass diese PathIsDirectoryEmpty () -Methode von shlwapi.dll auf Vista32 / 64- und XP32 / 64-Computern gut funktioniert, aber auf einigen Win7-Computern bombardiert. Es muss etwas mit Versionen von shlwapi.dll zu tun haben, die mit verschiedenen Windows-Versionen ausgeliefert werden. In acht nehmen.
Alex_P
7

Ich weiß nichts über die Leistungsstatistik in diesem Fall, aber haben Sie versucht, die Directory.GetFiles()statische Methode zu verwenden?

Es gibt ein String-Array zurück, das Dateinamen enthält (nicht FileInfos), und Sie können die Länge des Arrays auf die gleiche Weise wie oben überprüfen.

Cerebrus
quelle
Das gleiche Problem, es kann langsam sein, wenn es viele Dateien gibt ... aber es ist wahrscheinlich schneller als GetFileSystemInfos
Thomas Levesque
4

Ich bin mir sicher, dass die anderen Antworten schneller sind und Ihre Frage gefragt wurde, ob ein Ordner Dateien oder Ordner enthält oder nicht ... aber ich würde denken, dass die meisten Leute ein Verzeichnis als leer betrachten, wenn es keine Dateien enthält. dh es ist für mich immer noch "leer", wenn es leere Unterverzeichnisse enthält ... dies passt möglicherweise nicht für Ihre Verwendung, aber möglicherweise für andere!

  public bool DirectoryIsEmpty(string path)
  {
    int fileCount = Directory.GetFiles(path).Length;
    if (fileCount > 0)
    {
        return false;
    }

    string[] dirs = Directory.GetDirectories(path);
    foreach (string dir in dirs)
    {
      if (! DirectoryIsEmpty(dir))
      {
        return false;
      }
    }

    return true;
  }
Brad Parks
quelle
Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any()
Jonathan Gilbert
3

Sie müssen in jedem Fall auf die Festplatte gehen, um diese Informationen zu erhalten, und dies allein übertrumpft jede Objekterstellung und Array-Füllung.

Don Reba
quelle
1
Richtig, obwohl das Erstellen einiger Objekte das Nachschlagen zusätzlicher Metadaten auf der Festplatte erfordert, die möglicherweise nicht erforderlich sind.
Adam Rosenfield
Die ACL wäre sicher für jedes Objekt erforderlich. Daran führt kein Weg vorbei. Und wenn Sie diese nachschlagen müssen, können Sie auch andere Informationen in den MFT-Headern für die Dateien im Ordner lesen.
Don Reba
3

Mir ist keine Methode bekannt, mit der Sie kurz und bündig feststellen können, ob ein bestimmter Ordner andere Ordner oder Dateien enthält. Dabei wird Folgendes verwendet:

Directory.GetFiles(path);
&
Directory.GetDirectories(path);

Dies sollte die Leistung verbessern, da beide Methoden nur ein Array von Zeichenfolgen mit den Namen der Dateien / Verzeichnisse und nicht die gesamten FileSystemInfo-Objekte zurückgeben.

CraigTP
quelle
2

Vielen Dank an alle für die Antworten. Ich habe versucht, die Methoden Directory.GetFiles () und Directory.GetDirectories () zu verwenden. Gute Nachrichten! Die Leistung hat sich ~ zweimal verbessert! 229 Anrufe in 221 ms. Ich hoffe aber auch, dass es möglich ist, die Aufzählung aller Elemente im Ordner zu vermeiden. Stimmen Sie zu, dass immer noch der unnötige Job ausgeführt wird. Glaubst du nicht?

Nach allen Untersuchungen bin ich zu dem Schluss gekommen, dass unter reinem .NET eine weitere Optimierung unmöglich ist. Ich werde mit der FindFirstFile- Funktion von WinAPI spielen . Hoffe es wird helfen.

zhe
quelle
1
Was sind aus Interesse die Gründe, warum Sie für diese Operation eine so hohe Leistung benötigen?
Meandmycode
1
Anstatt Ihre eigene Frage zu beantworten, markieren Sie eine der richtigen Antworten als Antwort (wahrscheinlich die erste oder die klarste). Auf diese Weise sehen zukünftige Benutzer von stackoverflow die beste Antwort direkt unter Ihrer Frage!
Ray Hayes
2

Möglicherweise möchten Sie einige Zeit überprüfen, ob Dateien in Unterverzeichnissen vorhanden sind, und diese leeren Unterverzeichnisse ignorieren. In diesem Fall können Sie die folgende Methode verwenden:

public bool isDirectoryContainFiles(string path) {
    if (!Directory.Exists(path)) return false;
    return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any();
}
Leng Weh Seng
quelle
2

Leicht und einfach:

int num = Directory.GetFiles(pathName).Length;

if (num == 0)
{
   //empty
}
Matheus Miranda
quelle
0

Basierend auf Brad Parks Code:

    public static bool DirectoryIsEmpty(string path)
    {
        if (System.IO.Directory.GetFiles(path).Length > 0) return false;

        foreach (string dir in System.IO.Directory.GetDirectories(path))
            if (!DirectoryIsEmpty(dir)) return false;

        return true;
    }
Ángel Ibáñez
quelle
-1

Mein Code ist erstaunlich, es hat nur 00: 00: 00.0007143 weniger als eine Millisekunde mit 34 Dateien im Ordner gedauert

   System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
    sw.Start();

     bool IsEmptyDirectory = (Directory.GetFiles("d:\\pdf").Length == 0);

     sw.Stop();
     Console.WriteLine(sw.Elapsed);
Prashant Cholachagudda
quelle
Wenn Sie es mit 229 multiplizieren und GetDirectories () hinzufügen, erhalten Sie das gleiche Ergebnis wie bei mir :)
zhe
-1

Hier ist etwas, das Ihnen dabei helfen könnte. Ich habe es in zwei Iterationen geschafft.

 private static IEnumerable<string> GetAllNonEmptyDirectories(string path)
   {
     var directories =
        Directory.EnumerateDirectories(path, "*.*", SearchOption.AllDirectories)
        .ToList();

     var directoryList = 
     (from directory in directories
     let isEmpty = Directory.GetFiles(directory, "*.*", SearchOption.AllDirectories).Length == 0
     where !isEmpty select directory)
     .ToList();

     return directoryList.ToList();
   }
Gabriel Marius Popescu
quelle
-1

Da Sie sowieso mit einem DirectoryInfo-Objekt arbeiten werden, würde ich eine Erweiterung verwenden

public static bool IsEmpty(this DirectoryInfo directoryInfo)
{
    return directoryInfo.GetFileSystemInfos().Count() == 0;
}
The_Black_Smurf
quelle
-2

Benutze das. Es ist einfach.

Public Function IsDirectoryEmpty(ByVal strDirectoryPath As String) As Boolean
        Dim s() As String = _
            Directory.GetFiles(strDirectoryPath)
        If s.Length = 0 Then
            Return True
        Else
            Return False
        End If
    End Function
Puffgroovy
quelle
2
Einfach vielleicht. Aber falsch. Es gibt zwei Hauptfehler: Es erkennt nicht, ob sich Ordner im Pfad befinden, sondern nur Dateien, und es wird eine Ausnahme für einen Pfad ausgelöst, der nicht vorhanden ist. Es ist wahrscheinlich auch langsamer als das Original des OP, da ich ziemlich sicher bin, dass es alle Einträge erhält und sie filtert.
Andrew Barber