Как быстро проверить, если папка пуста (.NET)?



Я должен проверить, если каталог на диске пуст. Это означает, что он не содержит никаких папок/файлов. Я знаю, что есть простой способ. Мы получаем массив FileSystemInfo и проверяем, если количество элементов равно нулю. Что-то вроде этого:



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


этот подход, как представляется, ОК. Но!! Это очень, очень плохо с точки зрения производительности. GetFileSystemInfos () - это очень жесткий метод. На самом деле, он перечисляет все объекты файловой системы папки, получает все их свойства, создает объекты, заполняет типизированный массив и т. д. И все это только для того, чтобы просто проверить длину. Это глупо, не так ли?



Я только что профилировал такой код и определил, что ~250 вызовов такого метода выполняются за ~500 мс. это очень медленно и я считаю, что это можно сделать гораздо быстрее.



какие предложения?

712   17  

17 ответов:

есть новая функция в каталоге и DirectoryInfo в .NET 4, которая позволяет возвращать IEnumerable вместо массива и начинает возвращать результаты перед чтением всего содержимого каталога.

посмотреть здесь и здесь

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

EDIT: снова увидев этот ответ, я понимаю, что этот код можно сделать намного проще...

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

вот дополнительное быстрое решение, которое я, наконец, реализовал. Здесь я использую WinAPI и функции FindFirstFile,FindNextFile. Это позволяет избежать перечисления всех элементов в папке и останавливается сразу после обнаружения первого объекта в папке. Этот подход ~6(!!) в разы быстрее, чем описано выше. 250 звонков за 36 МС!

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

Я надеюсь, что это будет полезно для кого-то в будущем.

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

этот быстрый тест вернулся через 2 миллисекунды для папки, когда она пуста и содержит вложенные папки и файлы (5 папок с 5 файлами в каждой)

вы могли бы попробовать Directory.Exists(path) и Directory.GetFiles(path) - вероятно, меньше накладных расходов (нет объектов - только строки и т. д.).

Я использую это для папок и файлов (не знаю, насколько это оптимально)

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

Если вы не против оставить чистый C# и перейти на WinApi звонит, то вы, возможно, захотите, чтобы рассмотреть PathIsDirectoryEmpty (). Согласно MSDN, функция:

возвращает TRUE, если pszPath является пустым каталогом. Возвращает значение FALSE, если pszPath не является каталогом или содержит хотя бы один файл, отличный от "." или." .".

Это, кажется, функция, которая делает именно то, что вы хотите, так вероятно, он хорошо оптимизирован для этой задачи (хотя я этого не тестировал).

чтобы вызвать его из C#,pinvoke.net Сайт должен вам помочь. (К сожалению, он еще не описывает эту определенную функцию, но вы должны быть в состоянии найти некоторые функции с аналогичными аргументами и возвращаемым типом там и использовать их в качестве основы для вашего вызова. Если вы снова посмотрите в MSDN, он говорит, что DLL для импорта из is shlwapi.dll)

Я не знаю о статистике производительности на этом, но вы пробовали использовать Directory.GetFiles() статический метод ?

он возвращает массив строк, содержащий имена файлов (не FileInfos), и вы можете проверить длину массива таким же образом, как и выше.

Я уверен, что другие ответы быстрее, и ваш вопрос задан для того, содержит ли папка файлы или папки... но я думаю, что большую часть времени люди считают пустой каталог, если он не содержит файлов. т. е. он все еще "пуст" для меня, если он содержит пустые подкаталоги... это может не подходить для вашего использования, но может для других!

  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.GetFiles(path);
&
Directory.GetDirectories(path);

должно помочь производительности, так как оба эти метода будут возвращать только массив строк с именами файлов/каталогов, а не целые объекты FileSystemInfo.

спасибо всем за ответы. Я пытался использовать

некоторое время вы можете проверить, существуют ли какие-либо файлы внутри подкаталогов и игнорировать эти пустые подкаталоги; в этом случае вы можете использовать метод ниже:

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

легко и просто:

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

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

вы также должны обернуть свой тест в блок try/catch, чтобы убедиться, что вы правильно обрабатываете исключение DirectoryNotFoundException. Это классическое условие гонки в случае, если папка удаляется сразу после того, как вы проверили, существует ли она.

вот что может помочь вам сделать это. Мне удалось сделать это за две итерации.

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

мой код удивительно, что он просто взял 00:00:00.0007143 менее миллисекунды с 34 файла в папке

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

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

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

использовать это. Все очень просто.

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

Comments

    Ничего не найдено.