Как быстро проверить, если папка пуста (.NET)? - PullRequest
122 голосов
/ 16 апреля 2009

Я должен проверить, если каталог на диске пуст. Это означает, что он не содержит никаких папок / файлов. Я знаю, что есть простой метод. Мы получаем массив 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 мс. Это очень медленно, и я считаю, что это можно сделать гораздо быстрее.

Есть предложения?

Ответы [ 18 ]

253 голосов
/ 05 июня 2009

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

Смотрите здесь и там

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

РЕДАКТИРОВАТЬ: увидев этот ответ снова, я понимаю, что этот код можно сделать намного проще ...

public bool IsDirectoryEmpty(string path)
{
    return !Directory.EnumerateFileSystemEntries(path).Any();
}
28 голосов
/ 17 апреля 2009

Вот очень быстрое решение, которое я наконец-то реализовал. Здесь я использую 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();
}

Надеюсь, это будет кому-нибудь полезно в будущем.

18 голосов
/ 16 апреля 2009
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 файлов в каждой)

18 голосов
/ 16 апреля 2009

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

10 голосов
/ 30 мая 2016

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

    if(Directory.GetFileSystemEntries(path).Length == 0)
8 голосов
/ 05 июня 2009

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

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

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

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

7 голосов
/ 16 апреля 2009

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

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

4 голосов
/ 21 декабря 2011

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

  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;
  }
3 голосов
/ 23 января 2018

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

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

if (num == 0)
{
   //empty
}
3 голосов
/ 16 апреля 2009

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

...