2009-04-16 17 views
102

Devo controllare, se la directory su disco è vuota. Significa che non contiene cartelle/file. Lo so, c'è un metodo semplice. Otteniamo una serie di FileSystemInfo e controlliamo se il conteggio degli elementi è uguale a zero. Qualcosa del genere:Come controllare rapidamente se la cartella è vuota (.NET)?

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 filesystem della cartella, ottiene tutte le loro proprietà, crea oggetti, riempie array digitati ecc. Tutto ciò semplicemente per controllare semplicemente Length. È stupido, vero?

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

Qualche suggerimento?

+6

Per curiosità, perché vorresti controllare la directory 250 volte? – ya23

+1

@ ya23 Suppongo che si voglia controllare 250 directory differenti. Non uno solo 250 volte. –

risposta

25

Ecco la soluzione più veloce, che ho finalmente implementato.Qui sto usando WinAPI e funzioni FindFirstFile, TrovaNextFile. Permette di evitare l'enumerazione di tutti gli elementi nella cartella e le interruzioni subito dopo aver rilevato il primo oggetto nella cartella. Questo approccio è ~ 6 (!!) volte più veloce, rispetto a quanto descritto sopra. 250 chiamate in 36 ms!

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 possa essere utile per qualcuno in futuro.

+0

Grazie per aver condiviso la soluzione. – Greg

+3

È necessario aggiungere 'SetLastError = true' a' DllImport' per 'FindFirstFile' in modo che la chiamata' Marshal.GetHRForLastWin32Error() 'funzioni correttamente, come descritto nella sezione Note del [MSDN doc per GetHRForLastWin32Error() ] (http://msdn.microsoft.com/en-us/library/system.runtime.interopservices.marshal.gethrforlastwin32error.aspx). –

15

Si potrebbe provare Directory.Exists(path) e Directory.GetFiles(path) - probabilmente meno sovraccarico (nessun oggetto - solo stringhe ecc.).

+0

Come sempre, sei il più veloce dal grilletto! Sconfiggimi di qualche secondo lì! :-) – Cerebrus

+0

Eri più veloce di me ... maledetta mia attenzione ai dettagli ;-) –

+2

Non mi ha fatto nulla di buono, però; prima risposta, e l'unica senza un voto ;-( –

7

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

Restituisce un array di stringhe contenente nomi di file (non FileInfos) ed è possibile controllare la lunghezza dell'array nello stesso modo come sopra.

+0

nello stesso numero, può essere lento se ci sono molti file ... ma probabilmente è più veloce di GetFileSystemInfos –

3

In questo caso, è necessario accedere al disco rigido per queste informazioni e questo da solo supererà qualsiasi creazione di oggetti e riempimento di array.

+1

Vero, anche se la creazione di alcuni oggetti comporta la ricerca di ulteriori metadati sul disco che potrebbero non essere necessari –

+0

L'ACL sarebbe richiesto per ogni oggetto: non c'è modo di aggirarlo e una volta che si devono cercare quelle, si potrebbe anche leggere qualsiasi altra informazione nelle intestazioni MFT per i file nella cartella. –

19
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 è vuoto e quando contenente sottocartelle & file (5 cartelle con 5 file in ciascuna)

+2

Si potrebbe migliorare questo ritornando se 'dirs' è nessuno-vuoto subito, senza dover ottenere l'elenco dei file. – samjudson

+2

Sì, ma cosa succede se ci sei tu sabbie di file al suo interno? –

+2

Stai anche misurando il tempo di scrittura sulla console, che non è trascurabile. – ctusch

3

io non sono a conoscenza di un metodo che succintamente dire se una determinata cartella contiene tutte le cartelle o file, tuttavia, utilizzando:

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

dovrebbe aiutare le prestazioni dal momento che entrambi questi metodi restituire solo un array di stringhe con i nomi dei file/directory, piuttosto che tutto il FileSystemIn per oggetti.

2

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

Dopo tutte le indagini, ho raggiunto una conclusione, che sotto puro .NET ulteriore ottimizzazione è impossibile. Ho intenzione di giocare con la funzione FindFirstFile di WinAPI. Spero che ti sarà d'aiuto.

+1

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

+1

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

-1

Il mio codice è stupefacente appena ha preso 00: 00: 00.0007143 meno di milisecond 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); 
+0

In realtà, se lo si moltiplica per 229 e si aggiunge GetDirectories(), si otterrà lo stesso risultato, come il mio :) – zhe

8

Se non ti dispiace lasciando puro C# e andare per Winapi chiamate, allora si potrebbe 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 ciò che si desidera, quindi è probabilmente ottimizzata per tale compito (anche se non l'ho ancora testato).

Per chiamarlo da C#, il sito pinvoke.net dovrebbe aiutarti. (Sfortunatamente, non descrive ancora questa determinata 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 nel MSDN, dice che la DLL da importare è shlwapi.dll

+0

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

+3

Una nota per coloro che vogliono percorrere questa strada. Sembra che questo metodo PathIsDirectoryEmpty() di shlwapi.dll funzioni bene su macchine Vista32/64 e XP32/64, ma bombarda su alcune macchine Win7. Deve essere qualcosa a che fare con le versioni di shlwapi.dll fornite con diverse versioni di Windows. Attenzione. –

216

C'è una nuova funzionalità in Directory e DirectoryInfo in .NET 4 che consente di restituire un oggetto IEnumerable anziché un array e inizia a restituire i risultati prima di leggere tutto il contenuto della directory.

See here e there

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

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

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

dolce! amare una singola linea di soluzione di codice. upvoted. – pearcewg

+0

Mi piace questa soluzione, si può fare per controllare solo alcuni tipi di file? .Contains ("jpg") invece di .any() non sembra funzionare – Dennis

+2

@Dennis, puoi specificare un modello jolly nella chiamata a 'EnumerateFileSystemEntries', o usare' .Any (condizione) '(specificare la condizione come un'espressione lambda o un metodo che accetta un percorso come parametro). –

0

Si dovrebbe anche avvolgere la prova in un blocco try/catch per assicurarsi di gestire correttamente una DirectoryNotFoundException. Questa è una classica condizione di gara nel caso in cui la cartella venga eliminata subito dopo aver verificato se esistesse.

4

Sono sicuro che le altre risposte sono più veloci e la tua domanda chiedeva se una cartella contenesse file o cartelle ... ma penserei che la maggior parte 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 per il tuo utilizzo, ma potrebbe 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; 
    } 
-2

Utilizzare 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 
+1

Semplice, forse, ma errato. Ha due principali bug: non rileva se * le cartelle * si trovano nel percorso, solo file e genereranno un'eccezione su un percorso che non esiste. È probabile che sia effettivamente * più lento * rispetto al L'originale di OP, perché sono abbastanza sicuro che ottiene tutte le voci e le filtra. –

2

Un po 'di tempo potresti voler verificare se esistono dei file all'interno delle sottodirectory e ignorare quelle sottodirectory vuote; in questo caso è possibile utilizzare il metodo di seguito:

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

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

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

Ecco qualcosa che potrebbe aiutare 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(); 
    } 
0

facile e semplice:

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

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