C# перечислить все" листовые " подкаталоги с перечисленными каталогами
Всем доброе утро, У меня есть папка, которая содержит тысячи подкаталогов на разной глубине. Мне нужно перечислить все каталоги, которые не содержат подкаталогов (пресловутый "конец строки"). Хорошо, если они содержат файлы. Есть ли способ сделать это с помощью EnumerateDirectories?
Например, если полностью рекурсивный EnumerateDirectories возвращает:
/files/
/files/q
/files/q/1
/files/q/2
/files/q/2/examples
/files/7
/files/7/eb
/files/7/eb/s
/files/7/eb/s/t
Меня интересует только:
/files/q/1
/files/q/2/examples
/files/7/eb/s/t
2 ответа:
Это должно сработать:
var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories) .Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any());
Если вы хотите избежать вызова
EnumerateDirectories()
дважды для каждого каталога, вы можете реализовать его следующим образом:public IEnumerable<string> EnumerateLeafFolders(string root) { bool anySubfolders = false; foreach (var subfolder in Directory.EnumerateDirectories(root)) { anySubfolders = true; foreach (var leafFolder in EnumerateLeafFolders(subfolder)) yield return leafFolder; } if (!anySubfolders) yield return root; }
Я провел некоторые временные тесты, и для меня этот подход более чем в два раза быстрее, чем использование подхода Linq.
Я провел этот тест, используя сборку выпуска, выполняемую вне любого отладчика. Я запустил его на SSD, содержащем большое количество папок - общее количество листовых папок было 25035.
Мои результаты для второго запуска программы (Первый запуск был для предварительного нагрева дисковый кэш):
Calling Using linq. 1 times took 00:00:08.2707813 Calling Using yield. 1 times took 00:00:03.6457477 Calling Using linq. 1 times took 00:00:08.0668787 Calling Using yield. 1 times took 00:00:03.5960438 Calling Using linq. 1 times took 00:00:08.1501002 Calling Using yield. 1 times took 00:00:03.6589386 Calling Using linq. 1 times took 00:00:08.1325582 Calling Using yield. 1 times took 00:00:03.6563730 Calling Using linq. 1 times took 00:00:07.9994754 Calling Using yield. 1 times took 00:00:03.5616040 Calling Using linq. 1 times took 00:00:08.0803573 Calling Using yield. 1 times took 00:00:03.5892681 Calling Using linq. 1 times took 00:00:08.1216921 Calling Using yield. 1 times took 00:00:03.6571429 Calling Using linq. 1 times took 00:00:08.1437973 Calling Using yield. 1 times took 00:00:03.6606362 Calling Using linq. 1 times took 00:00:08.0058955 Calling Using yield. 1 times took 00:00:03.6477621 Calling Using linq. 1 times took 00:00:08.1084669 Calling Using yield. 1 times took 00:00:03.5875057
Как вы можете видеть, использование метода доходности значительно быстрее. (Вероятно, потому, что он не перечисляет каждую папку дважды.)
Мой тестовый код:
using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Linq; namespace Demo { class Program { private void run() { string root = "F:\\TFROOT"; Action test1 = () => leafFolders1(root).Count(); Action test2 = () => leafFolders2(root).Count(); for (int i = 0; i < 10; ++i) { test1.TimeThis("Using linq."); test2.TimeThis("Using yield."); } } static void Main() { new Program().run(); } static IEnumerable<string> leafFolders1(string root) { var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories) .Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any()); return folderWithoutSubfolder; } static IEnumerable<string> leafFolders2(string root) { bool anySubfolders = false; foreach (var subfolder in Directory.EnumerateDirectories(root)) { anySubfolders = true; foreach (var leafFolder in leafFolders2(subfolder)) yield return leafFolder; } if (!anySubfolders) yield return root; } } static class DemoUtil { public static void Print(this object self) { Console.WriteLine(self); } public static void Print(this string self) { Console.WriteLine(self); } public static void Print<T>(this IEnumerable<T> self) { foreach (var item in self) Console.WriteLine(item); } public static void TimeThis(this Action action, string title, int count = 1) { var sw = Stopwatch.StartNew(); for (int i = 0; i < count; ++i) action(); Console.WriteLine("Calling {0} {1} times took {2}", title, count, sw.Elapsed); } } }