【问题标题】:C# List all "leaf" subdirectories with EnumerateDirectoriesC# 使用 EnumerateDirectories 列出所有“叶子”子目录
【发布时间】:2013-07-22 11:17:48
【问题描述】:

大家早上好, 我有一个文件夹,其中包含数千个不同深度的子目录。我需要列出所有不包含子目录的目录(众所周知的“行尾”)。如果它们包含文件很好。有没有办法用 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

【问题讨论】:

    标签: c# directory enumerate


    【解决方案1】:

    这应该可行:

    var folderWithoutSubfolder = Directory.EnumerateDirectories(root, "*.*", SearchOption.AllDirectories)
         .Where(f => !Directory.EnumerateDirectories(f, "*.*", SearchOption.TopDirectoryOnly).Any());
    

    【讨论】:

      【解决方案2】:

      如果你想避免为每个目录调用两次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 上运行它 - LEAF 文件夹的总数为 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);
              }
          }
      }
      

      【讨论】:

      • EnumerateDirectories 被懒惰地评估,所以蒂姆的答案中的额外调用非常便宜。当我将您的代码与 Tim 的代码进行基准测试时,Tim 的运行时间不到一半。我想这是因为递归地使用迭代器会增加很多开销。
      • @Brian 您是否多次运行测试以消除第一次运行时磁盘缓存导致的伪影?
      • 是的。在开始之前,我运行了几百次测试,进行了优化编译,并在启动计时器之前运行了每个测试一次(以避免抖动伪影)。
      • @Brian 我发现我的方法运行速度快了两倍多。我将附上我的测试代码,以便您查看。您是否确保在调试器外部运行它(否则即使它编译为发行版,它也会在调试模式下运行)
      • 我尝试将您的代码更改为不使用递归(请参阅 pastebin.com/eZdbM0i6 )。当我对它进行基准测试时,它的运行速度比 Tim 的快 10%。如果我添加对 Directory.EnumerateDirectories 的额外调用,这些好处大多不会消失,所以我认为它们与 LINQ 开销有关。无论如何,对 EnumerateDirectories 的无关调用在您的系统上可能会更昂贵。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-02-26
      • 2014-09-12
      • 1970-01-01
      • 2012-03-31
      • 2017-05-01
      • 2011-11-09
      • 2011-11-09
      相关资源
      最近更新 更多