Я должен проверить, пуст ли каталог на диске. Это означает, что он не содержит никаких папок / файлов. Я знаю, что есть простой способ. Получаем массив 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 () - очень сложный метод. Фактически, он перечисляет все объекты файловой системы папки, получает все их свойства, создает объекты, заполняет типизированный массив и т.д. И все это просто для проверки Length. Это же глупо, правда?
Я только что профилировал такой код и определил, что ~ 250 вызовов такого метода выполняются за ~ 500 мс. Это очень медленно, и я считаю, что это можно сделать намного быстрее.
Какие-либо предложения?
Ответы:
Существует новая функция
Directory
иDirectoryInfo
в .NET 4 , что позволяет им возвращатьIEnumerable
вместо массива, и начать возвращать результаты , прежде чем читать все содержимое каталога.Directory.EnumerateFileSystemEntries
перегрузки методов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(); }
источник
EnumerateFileSystemEntries
или использовать.Any(condition)
(указать условие как лямбда-выражение или как метод, который принимает путь в качестве параметра).return !items.GetEnumerator().MoveNext();
Вот сверхбыстрое решение, которое я наконец реализовал. Здесь я использую 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(); }
Надеюсь, это будет кому-то полезно в будущем.
источник
SetLastError = true
вDllImport
for , как описано в разделе «Примечания» документа MSDN для GetHRForLastWin32Error () .FindFirstFile
Marshal.GetHRForLastWin32Error()
Вы можете попробовать
Directory.Exists(path)
иDirectory.GetFiles(path)
- возможно, с меньшими накладными расходами (без объектов - только строки и т. Д.).источник
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 файлов в каждой).
источник
Я использую это для папок и файлов (не знаю, оптимально ли это)
if(Directory.GetFileSystemEntries(path).Length == 0)
источник
Если вы не против оставить чистый C # и перейти на вызовы WinApi , то вы можете рассмотреть функцию PathIsDirectoryEmpty () . Согласно MSDN, функция:
Кажется, это функция, которая делает именно то, что вы хотите, поэтому она, вероятно, хорошо оптимизирована для этой задачи (хотя я этого не тестировал).
Чтобы вызвать его из C #, вам должен помочь сайт pinvoke.net . (К сожалению, он еще не описывает эту конкретную функцию, но вы сможете найти там некоторые функции с аналогичными аргументами и типом возвращаемого значения и использовать их в качестве основы для своего вызова. Если вы снова посмотрите в MSDN, там говорится, что DLL для импорта из
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.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any()
В любом случае вам придется обратиться к жесткому диску для получения этой информации, и только это превзойдет любое создание объекта и заполнение массива.
источник
Я не знаю метода, который кратко сообщит вам, содержит ли данная папка какие-либо другие папки или файлы, однако, используя:
Directory.GetFiles(path); & Directory.GetDirectories(path);
должен повысить производительность, поскольку оба этих метода будут возвращать только массив строк с именами файлов / каталогов, а не целые объекты FileSystemInfo.
источник
Легко и просто:
int num = Directory.GetFiles(pathName).Length; if (num == 0) { //empty }
источник
Спасибо всем за ответы. Я пробовал использовать методы Directory.GetFiles () и Directory.GetDirectories () . Хорошие новости! Производительность улучшилась ~ вдвое! 229 звонков за 221 мс. Но также я надеюсь, что можно избежать перечисления всех элементов в папке. Согласитесь, что ненужная работа все еще выполняется. Вы так не думаете?
После всех исследований я пришел к выводу, что под чистым .NET дальнейшая оптимизация невозможна. Я собираюсь поиграть с функцией WinAPI FindFirstFile . Надеюсь, это поможет.
источник
Когда-нибудь вы можете захотеть проверить, существуют ли какие-либо файлы внутри подкаталогов, и проигнорировать эти пустые подкаталоги; в этом случае вы можете использовать метод ниже:
public bool isDirectoryContainFiles(string path) { if (!Directory.Exists(path)) return false; return Directory.EnumerateFiles(path, "*", SearchOption.AllDirectories).Any(); }
источник
На основе кода Брэда Паркса :
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; }
источник
Мой код потрясающий, он занял 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);
источник
Вот кое-что, что может вам в этом помочь. Мне удалось сделать это за две итерации.
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(); }
источник
Поскольку вы все равно собираетесь работать с объектом DirectoryInfo, я бы выбрал расширение
public static bool IsEmpty(this DirectoryInfo directoryInfo) { return directoryInfo.GetFileSystemInfos().Count() == 0; }
источник
Использовать это. Это просто.
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
источник