Come verificare rapidamente se la cartella è vuota (.NET)?


141

Devo verificare se la directory sul disco è vuota. Significa che non contiene cartelle / file. So che esiste un metodo semplice. Otteniamo la matrice di FileSystemInfo e controlliamo se il conteggio degli elementi è uguale a zero. Qualcosa del genere:

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

Questo approccio sembra OK. MA!! È molto, molto male dal punto di vista delle prestazioni. GetFileSystemInfos () è un metodo molto difficile. In realtà, enumera tutti gli oggetti del file system della cartella, ottiene tutte le loro proprietà, crea oggetti, riempie l'array tipizzato ecc. E tutto questo solo per controllare semplicemente Lunghezza. È stupido, no?

Ho appena profilato tale codice e determinato che ~ 250 chiamate di tale metodo vengono eseguite in ~ 500ms. Questo è molto lento e credo che sia possibile farlo molto più velocemente.

Eventuali suggerimenti?


7
Per curiosità, perché ti piacerebbe controllare la directory 250 volte?
— ya23

2
@ ya23 Suppongo che uno vorrebbe controllare 250 directory diverse. Non uno solo 250 volte.
— Mathieu Pagé,

Risposte:


283

Esiste una nuova funzionalità in Directorye DirectoryInfoin .NET 4 che consente loro di restituire IEnumerableun array anziché un array e iniziare a restituire risultati prima di leggere tutto il contenuto della directory.

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

EDIT: vedendo di nuovo quella risposta, mi rendo conto che questo codice può essere reso molto più semplice ...

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

Mi piace questa soluzione, può essere fatta per verificare solo alcuni tipi di file? .Contains ("jpg") invece di .any () non sembra funzionare
— Dennis

5
@Dennis, è possibile specificare un modello jolly nella chiamata a EnumerateFileSystemEntries, oppure utilizzare .Any(condition)(specificare la condizione come espressione lambda o come metodo che accetta un percorso come parametro).
— Thomas Levesque,

Il typecast può essere rimosso dal primo esempio di codice:return !items.GetEnumerator().MoveNext();
— gary

1
@gary, se lo fai, l'enumeratore non verrà eliminato, quindi bloccherà la directory fino a quando l'enumeratore non sarà raccolto.
— Thomas Levesque,

Questo sembra funzionare bene per le directory contenenti file, ma se la directory contiene altri director, torna dicendo che è vuota.
— Kairan,

32

Ecco la soluzione extra veloce, che finalmente ho implementato. Qui sto usando WinAPI e le funzioni FindFirstFile , FindNextFile . Permette di evitare l'enumerazione di tutti gli elementi nella Cartella e si arresta subito dopo aver rilevato il primo oggetto nella Cartella . Questo approccio è ~ 6 (!!) volte più veloce di quanto descritto sopra. 250 chiamate 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();
}

Spero che sarà utile per qualcuno in futuro.


Grazie per aver condiviso la tua soluzione.
— Greg,

3
È necessario aggiungere SetLastError = truea DllImportper FindFirstFileaffinché la Marshal.GetHRForLastWin32Error()chiamata funzioni correttamente, come descritto nella sezione Note del documento MSDN per GetHRForLastWin32Error () .
— Joel V. Earnest-DeYoung,

Penso che la seguente risposta sia un po 'migliore in quanto cerca anche i file nelle sottodirectory stackoverflow.com/questions/724148/…
— Mayank,

21

Potresti provare Directory.Exists(path)e Directory.GetFiles(path)- probabilmente meno sovraccarico (niente oggetti - solo stringhe ecc.).


Come sempre, sei più veloce dal grilletto! Battimi di qualche secondo lì! :-)
— Cerebrus

Siete stati entrambi più veloci di me ... accidenti alla mia attenzione ai dettagli ;-)
— Eoin Campbell,

2
Non mi ha fatto nulla di buono, comunque; prima risposta, e l'unica senza voto
— ;-(

Non risolto ... qualcuno ha un'ascia da macinare, pensa
— Marc Gravell

1
Non credo che GetFiles otterrà un elenco di Directories, quindi sembra essere una buona idea fare un controllo anche per GetDirectories
— Kairan,

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

Questo test rapido è tornato in 2 millisecondi per la cartella quando è vuota e quando contiene sottocartelle e file (5 cartelle con 5 file ciascuna)


3
Potresti migliorare questo restituendo se 'dirs' non è immediatamente vuoto, senza dover ottenere l'elenco dei file.
— Samjudson,

3
Sì, e se ci fossero migliaia di file in esso?
— Thomas Levesque,

3
Stai anche misurando il tempo per scrivere sulla console, il che non è trascurabile.
— ctusch

11

Lo uso per cartelle e file (non so se è ottimale)

    if(Directory.GetFileSystemEntries(path).Length == 0)

8

Se non ti dispiace lasciare C # puro e andare per le chiamate WinApi , potresti prendere in considerazione la funzione PathIsDirectoryEmpty () . Secondo MSDN, la funzione:

Restituisce VERO se pszPath è una directory vuota. Restituisce FALSE se pszPath non è una directory o se contiene almeno un file diverso da "." o "..".

Questa sembra essere una funzione che fa esattamente quello che vuoi, quindi è probabilmente ben ottimizzata per quel compito (anche se non l'ho provato).

Per chiamarlo da C #, il sito pinvoke.net dovrebbe aiutarti. (Sfortunatamente, non descrive ancora questa certa funzione, ma dovresti essere in grado di trovare alcune funzioni con argomenti simili e restituire il tipo lì e usarle come base per la tua chiamata. Se guardi di nuovo in MSDN, dice che la DLL da cui importare è shlwapi.dll)


Grande idea. Non sapevo di questa funzione. Proverò a confrontare le sue prestazioni con il mio approccio, che ho descritto sopra. Se lo farebbe più velocemente, lo riutilizzerò nel mio codice. Grazie.
— zhe

4
Una nota per chi vuole seguire questa strada. Sembra che questo metodo PathIsDirectoryEmpty () di shlwapi.dll funzioni correttamente su macchine Vista32 / 64 e XP32 / 64, ma bombe su alcune macchine Win7. Deve avere a che fare con le versioni di shlwapi.dll fornite con versioni diverse di Windows. Attenzione.
— Alex_P

7

Non conosco le statistiche sulle prestazioni di questo, ma hai provato a utilizzare il Directory.GetFiles()metodo statico?

Restituisce una matrice di stringhe contenente nomi di file (non FileInfos) e puoi controllare la lunghezza della matrice come sopra.


stesso problema, può essere lento se ci sono molti file ... ma è probabilmente più veloce di GetFileSystemInfos
— Thomas Levesque,

4

Sono sicuro che le altre risposte sono più veloci e la tua domanda ha chiesto se una cartella contenesse o meno file o cartelle ... ma penso che il più delle volte le persone considererebbero una directory vuota se non contiene file. cioè è ancora "vuoto" per me se contiene sottodirectory vuote ... questo potrebbe non essere adatto al tuo utilizzo, ma potrebbe essere per gli altri!

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

Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any()
— Jonathan Gilbert,

3

Dovrai andare sul disco rigido per queste informazioni in ogni caso, e questo da solo vincerà qualsiasi creazione di oggetti e riempimento di array.


1
È vero, anche se la creazione di alcuni oggetti comporta la ricerca di metadati extra sul disco che potrebbero non essere necessari.
— Adam Rosenfield,

L'ACL sarebbe richiesto per ogni oggetto di sicuro. Non c'è modo di aggirarlo. E una volta che hai cercato quelli, potresti anche leggere qualsiasi altra informazione nelle intestazioni MFT per i file nella cartella.
— Don Reba,

3

Non sono a conoscenza di un metodo che ti dirà in modo succinto se una determinata cartella contiene altre cartelle o file, tuttavia, usando:

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

dovrebbe aiutare le prestazioni poiché entrambi questi metodi restituiranno solo una matrice di stringhe con i nomi dei file / directory anziché interi oggetti FileSystemInfo.


3

Facile e semplice:

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

if (num == 0)
{
   //empty
}

2

Grazie a tutti per le risposte. Ho provato a utilizzare i metodi Directory.GetFiles () e Directory.GetDirectories () . Buone notizie! Le prestazioni sono migliorate ~ due volte! 229 chiamate in 221ms. Ma spero anche che sia possibile evitare l'enumerazione di tutti gli elementi nella cartella. D'accordo, che è ancora in esecuzione il lavoro non necessario. Non la pensi così?

Dopo tutte le indagini, sono giunto a una conclusione che sotto puro .NET un'ulteriore ottimizzazione è impossibile. Ho intenzione di giocare con la funzione FindFirstFile di WinAPI . Spero che possa essere d'aiuto.


1
Per interesse, quali sono i motivi per cui hai bisogno di prestazioni così elevate per questa operazione?
— meandmycode

1
Invece di rispondere alla tua domanda, segna una delle risposte corrette come risposta (probabilmente la prima pubblicata o la più chiara). In questo modo i futuri utenti di StackOverflow vedranno la risposta migliore proprio sotto la tua domanda!
— Ray Hayes,

2

Qualche volta potresti voler verificare l'esistenza di file all'interno delle sottodirectory e ignorare quelle sottodirectory vuote; in questo caso puoi usare il metodo seguente:

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

0

Basato sul codice Brad Parks :

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

-1

Il mio codice è sorprendente, ci sono voluti solo 00: 00: 00.0007143 meno di un millisecondo con 34 file nella cartella

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

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

     sw.Stop();
     Console.WriteLine(sw.Elapsed);

In realtà, se lo moltiplichi per 229 e aggiungi GetDirectories (), otterrai lo stesso risultato del mio :)
— zhe

-1

Ecco qualcosa che potrebbe aiutarti a farlo. Sono riuscito a farlo in due iterazioni.

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

-1

Dato che lavorerai comunque con un oggetto DirectoryInfo, preferirei un'estensione

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

-2

Usa questo. È semplice.

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

2
Semplice, forse. Ma non corretto Ha due bug principali: Non rileva se ci sono cartelle nel percorso, solo file e genererà un'eccezione su un percorso che non esiste. È anche probabile che sia più lento dell'originale dell'OP, perché sono abbastanza sicuro che ottiene tutte le voci e le filtra.
— Andrew Barber,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.