【问题标题】:Searching for file in directories recursively递归搜索目录中的文件
【发布时间】:2012-04-07 11:22:40
【问题描述】:

我有以下代码通过目录递归搜索文件,该目录将所有 xml 文件的列表返回给我。一切正常,除了根目录中的 xml 文件不包含在列表中。

我明白为什么,因为它所做的第一件事是获取根目录中的目录,然后获取文件,因此缺少根目录上的 GetFiles() 调用。我尝试在 foreach 之前包含 GetFiles() 调用,但结果与我预期的不同。

public static ArrayList DirSearch(string sDir)
{
    try
    {
        foreach (string d in Directory.GetDirectories(sDir))
        {
            foreach (string f in Directory.GetFiles(d, "*.xml"))
            {
                string extension = Path.GetExtension(f);
                if (extension != null && (extension.Equals(".xml")))
                {
                fileList.Add(f);
                }
            }
            DirSearch(d);
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    return fileList;
}

我的目录结构是这样的:

RootDirectory
        test1.0.xml
            test1.1.xml
            test1.2.xml
  2ndLevDir
            test2.0.xml
            test2.1.xml
  3rdLevDir
               test3.0.xml
               test3.1.xml

代码返回:

test2.0.xml
test2.1.xml
test3.0.xml
test3.1.xml

我想退回所有文件,包括:

test1.0.xml
test1.1.xml
test1.2.xml

递归的诗句不太好。任何指针将不胜感激。

【问题讨论】:

标签: c# recursion


【解决方案1】:

您可以使用this overload of Directory.GetFiles 为您搜索子目录,例如:

string[] files = Directory.GetFiles(sDir, "*.xml", SearchOption.AllDirectories);

这样只能搜索一个扩展名,但您可以使用以下内容:

var extensions = new List<string> { ".txt", ".xml" };
string[] files = Directory.GetFiles(sDir, "*.*", SearchOption.AllDirectories)
                    .Where(f => extensions.IndexOf(Path.GetExtension(f)) >= 0).ToArray();

选择具有所需扩展名的文件(注意,扩展名区分大小写)。


在某些情况下,可能需要使用 Directory.EnumerateFiles Method 枚举文件:

foreach(string f in Directory.EnumerateFiles(sDir, "*.xml", SearchOption.AllDirectories))
{
    // do something
}

如果代码在没有适当访问权限的帐户下运行,请参阅文档以了解可能引发的异常,例如 UnauthorizedAccessException。

如果 UnauthorizedAccessException 有问题,请在Directory.EnumerateFiles => UnauthorizedAccessException 上查看详细答案。

【讨论】:

    【解决方案2】:

    这会递归返回所有 xml 文件:

    var allFiles = Directory.GetFiles(path, "*.xml", SearchOption.AllDirectories);
    

    【讨论】:

      【解决方案3】:

      试试下面的方法:

      public static IEnumerable<string> GetXMLFiles(string directory)
      {
          List<string> files = new List<string>();
      
          try
          {
              files.AddRange(Directory.GetFiles(directory, "*.xml", SearchOption.AllDirectories));
          }
          catch (Exception ex)
          {
              Console.WriteLine(ex.Message);
          }
      
          return files;
      }
      

      【讨论】:

        【解决方案4】:

        您正在创建三个列表,而不是使用一个(您不使用DirSearch(d) 的返回值)。您可以使用列表作为参数来保存状态:

        static void Main(string[] args)
        {
          var list = new List<string>();
          DirSearch(list, ".");
        
          foreach (var file in list)
          {
            Console.WriteLine(file);
          }
        }
        
        public static void DirSearch(List<string> files, string startDirectory)
        {
          try
          {
            foreach (string file in Directory.GetFiles(startDirectory, "*.*"))
            {
              string extension = Path.GetExtension(file);
        
              if (extension != null)
              {
                files.Add(file);
              }
            }
        
            foreach (string directory in Directory.GetDirectories(startDirectory))
            {
              DirSearch(files, directory);
            }
          }
          catch (System.Exception e)
          {
            Console.WriteLine(e.Message);
          }
        }
        

        【讨论】:

          【解决方案5】:

          您需要将文件的循环移到文件夹的循环之外。此外,您需要将保存文件集合的数据结构传递给方法的每次调用。这样所有文件都进入一个列表。

          public static List<string> DirSearch(string sDir, List<string> files)
          {
            foreach (string f in Directory.GetFiles(sDir, "*.xml"))
            {
              string extension = Path.GetExtension(f);
              if (extension != null && (extension.Equals(".xml")))
              {
                files.Add(f);
              }
            }
            foreach (string d in Directory.GetDirectories(sDir))
            {
              DirSearch(d, files);
            }
            return files;
          }
          

          那就这样称呼吧。

          List<string> files = DirSearch("c:\foo", new List<string>());
          

          更新:

          我不知道,在我阅读其他答案之前,已经有一个内置机制可以做到这一点。如果您有兴趣了解如何修改您的代码以使其正常工作,我会留下我的答案。

          【讨论】:

            【解决方案6】:

            您应该在目录循环之前或之后对文件进行循环,但不要像您所做的那样嵌套在其中。

            foreach (string f in Directory.GetFiles(d, "*.xml"))
            {
                string extension = Path.GetExtension(f);
                if (extension != null && (extension.Equals(".xml")))
                {
                    fileList.Add(f);
                }
            } 
            
            foreach (string d in Directory.GetDirectories(sDir))
            {
                DirSearch(d);
            }
            

            【讨论】:

              【解决方案7】:

              你可以这样做:

              foreach (var file in Directory.GetFiles(MyFolder, "*.xml", SearchOption.AllDirectories))
                      {
                          // do something with this file
                      }
              

              【讨论】:

                【解决方案8】:

                我尝试了此处列出的其他一些解决方案,但在单元测试期间,代码会抛出我想忽略的异常。我最终创建了以下递归搜索方法,该方法将忽略某些异常,例如 PathTooLongException 和 UnauthorizedAccessException。

                    private IEnumerable<string> RecursiveFileSearch(string path, string pattern, ICollection<string> filePathCollector = null)
                    {
                        try
                        {
                            filePathCollector = filePathCollector ?? new LinkedList<string>();
                
                            var matchingFilePaths = Directory.GetFiles(path, pattern);
                
                            foreach(var matchingFile in matchingFilePaths)
                            {
                                filePathCollector.Add(matchingFile);
                            }
                
                            var subDirectories = Directory.EnumerateDirectories(path);
                
                            foreach (var subDirectory in subDirectories)
                            {
                                RecursiveFileSearch(subDirectory, pattern, filePathCollector);
                            }
                
                            return filePathCollector;
                        }
                        catch (Exception error)
                        {
                            bool isIgnorableError = error is PathTooLongException ||
                                error is UnauthorizedAccessException;
                
                            if (isIgnorableError)
                            {
                                return Enumerable.Empty<string>();
                            }
                
                            throw error;
                        }
                    }
                

                【讨论】:

                  【解决方案9】:

                  使用 EnumerateFiles 获取嵌套目录中的文件。 使用 AllDirectories 递归遍历整个目录。

                  using System;
                  using System.IO;
                  
                  class Program
                  {
                      static void Main()
                      {
                      // Call EnumerateFiles in a foreach-loop.
                      foreach (string file in Directory.EnumerateFiles(@"c:\files",
                          "*.xml",
                          SearchOption.AllDirectories))
                      {
                          // Display file path.
                          Console.WriteLine(file);
                      }
                      }
                  }
                  

                  【讨论】:

                    【解决方案10】:

                    出于文件和目录搜索的目的,我想提供使用专门的多线程 .NET 库,该库拥有广泛的搜索机会并且运行速度非常快。

                    您可以在 GitHub 上找到有关库的所有信息:https://github.com/VladPVS/FastSearchLibrary

                    如果你想下载它,你可以在这里下载:https://github.com/VladPVS/FastSearchLibrary/releases

                    如果您有任何问题,请询问他们。

                    这是如何使用它的一个示范性示例:

                    class Searcher
                    {
                        private static object locker = new object(); 
                    
                        private FileSearcher searcher;
                    
                        List<FileInfo> files;
                    
                        public Searcher()
                        {
                            files = new List<FileInfo>(); // create list that will contain search result
                        }
                    
                        public void Startsearch()
                        {
                            CancellationTokenSource tokenSource = new CancellationTokenSource();
                            // create tokenSource to get stop search process possibility
                    
                            searcher = new FileSearcher(@"C:\", (f) =>
                            {
                                return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
                            }, tokenSource);  // give tokenSource in constructor
                    
                    
                            searcher.FilesFound += (sender, arg) => // subscribe on FilesFound event
                            {
                                lock (locker) // using a lock is obligatorily
                                {
                                    arg.Files.ForEach((f) =>
                                    {
                                        files.Add(f); // add the next received file to the search results list
                                        Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
                                    });
                    
                                    if (files.Count >= 10) // one can choose any stopping condition
                                        searcher.StopSearch();
                                }
                            };
                    
                            searcher.SearchCompleted += (sender, arg) => // subscribe on SearchCompleted event
                            {
                                if (arg.IsCanceled) // check whether StopSearch() called
                                    Console.WriteLine("Search stopped.");
                                else
                                    Console.WriteLine("Search completed.");
                    
                                Console.WriteLine($"Quantity of files: {files.Count}"); // show amount of finding files
                            };
                    
                            searcher.StartSearchAsync();
                            // start search process as an asynchronous operation that doesn't block the called thread
                        }
                    }
                    

                    【讨论】:

                    • 哎呀,这看起来像一个非常昂贵的搜索,特别是对于可以通过正常框架可能性实现的东西(并且正则表达式过滤可以用作枚举文件结果的 where 语句),更不用说消费者必须实现锁:(
                    • 当然可以使用简单搜索。像这样: List files = FileSearcher.GetFilesFast(@"C:\Users", "SomePattern.txt");此外,当你找到系统文件时,“正常的框架可能性”会抛出 UnauthorizedAccessException,但这个库会抑制它。
                    • @Icepickle,我建议您将此库的方法与解决此问题的代码进行比较。尤其是工作速度。
                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2016-09-06
                    • 2013-06-17
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多