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.