【问题标题】:Quickest way in C# to find a file in a directory with over 20,000 files在 C# 中查找包含超过 20,000 个文件的目录中的文件的最快方法
【发布时间】:2010-10-17 09:05:23
【问题描述】:

我有一项工作每天晚上运行,从根目录下有超过 20,000 个子文件夹的目录中提取 xml 文件。这是结构的样子:

rootFolder/someFolder/someSubFolder/xml/myFile.xml
rootFolder/someFolder/someSubFolder1/xml/myFile1.xml
rootFolder/someFolder/someSubFolderN/xml/myFile2.xml
rootFolder/someFolder1
rootFolder/someFolderN

所以看上面,结构总是一样的——一个根文件夹,然后是两个子文件夹,然后是一个xml目录,然后是xml文件。 我只知道 rootFolder 的名称和 xml 目录。

下面的代码遍历所有目录,速度极慢。关于如何优化搜索的任何建议,尤其是在目录结构已知的情况下?

string[] files = Directory.GetFiles(@"\\somenetworkpath\rootFolder", "*.xml", SearchOption.AllDirectories);

【问题讨论】:

  • 您是在寻找特定的 xml 文件还是想要所有这些文件的列表?
  • 我正在寻找所有的 xml 文件
  • 如果 xml 文件列表不随时间变化,您可以只构建一次列表并从列表中读取所有文件。但你可能知道。

标签: c# .net file-io


【解决方案1】:

您最有可能使用 GetDirectories,而不是执行 GetFiles 和蛮力搜索,首先获取“第一个子文件夹”的列表,遍历这些目录,然后对子文件夹重复该过程,遍历它们,最后查找xml文件夹,最后查找.xml文件。

现在,至于性能,速度会有所不同,但首先搜索目录,然后获取文件应该会有很大帮助!

更新

好的,我做了一些快速测试,您实际上可以比我想象的更进一步优化它。

以下代码 sn-p 将搜索目录结构并在整个目录树中查找所有“xml”文件夹。

string startPath = @"C:\Testing\Testing\bin\Debug";
string[] oDirectories = Directory.GetDirectories(startPath, "xml", SearchOption.AllDirectories);
Console.WriteLine(oDirectories.Length.ToString());
foreach (string oCurrent in oDirectories)
    Console.WriteLine(oCurrent);
Console.ReadLine();

如果您将其放入测试控制台应用程序中,您将看到它输出结果。

现在,一旦你有了这个,只需在找到的每个目录中查找你的 .xml 文件。

【讨论】:

  • 如果我想在 xml 文件中搜索特定文本并只收集这些文件,如何快速有效地完成?
【解决方案2】:

我创建了一个递归方法GetFolders 使用Parallel.ForEach 来查找所有命名为变量yourKeyword 的文件夹

List<string> returnFolders = new List<string>();
object locker = new object();

Parallel.ForEach(subFolders, subFolder =>
{
    if (subFolder.ToUpper().EndsWith(yourKeyword))
    {
        lock (locker)
        {
            returnFolders.Add(subFolder);
        }
    }
    else
    {
        lock (locker)
        {
            returnFolders.AddRange(GetFolders(Directory.GetDirectories(subFolder)));
        }
    }
});

return returnFolders;

【讨论】:

    【解决方案3】:

    是否有与 xml 文件夹处于同一级别的其他目录?如果是这样,如果您自己进行搜索并从搜索中消除该级别,您可能会加快搜索速度。

            System.IO.DirectoryInfo root = new System.IO.DirectoryInfo(rootPath);
            List<System.IO.FileInfo> xmlFiles=new List<System.IO.FileInfo>();
    
            foreach (System.IO.DirectoryInfo subDir1 in root.GetDirectories())
            {
                foreach (System.IO.DirectoryInfo subDir2 in subDir1.GetDirectories())
                {
                    System.IO.DirectoryInfo xmlDir = new System.IO.DirectoryInfo(System.IO.Path.Combine(subDir2.FullName, "xml"));
    
                    if (xmlDir.Exists)
                    {
                        xmlFiles.AddRange(xmlDir.GetFiles("*.xml"));
                    }
                }
            }
    

    【讨论】:

      【解决方案4】:

      我想不出 C# 中有什么更快的方法,但是您是否为该文件系统启用了索引?

      【讨论】:

      • 索引对于简单的遍历应该没有多大作用。
      【解决方案5】:

      我认为这会产生很大不同的唯一方法是从蛮力搜索中改变并使用一些第三方或操作系统索引例程来加快返回速度。这样搜索就可以从您的应用离线完成。

      但我也建议您尽可能寻找更好的方法来构建这些数据。

      【讨论】:

        【解决方案6】:

        FindFirstFile/FindNextFile/FindClose 上使用 P/Invoke 并避免创建大量 FileInfo 实例的开销。

        但这将是一项艰巨的工作(您必须自己处理文件与目录的所有处理以及递归)。因此,请尝试一些简单的方法(Directory.GetFiles()、Directory.GetDirectories()),然后开始工作。如果速度太慢,请考虑替代方案(但始终衡量,太容易使其变慢)。

        【讨论】:

        • 你知道一些用于 .net 的 FindFirstFile/FindNextFile/FindClose 包装器,它充当 IEnumerable?
        • @DanielMošmondor Systen,IO.Directoey 现在有这样的方法(在 .NET 4 中添加)。例如。 EnumerateDirectories.
        • 这不是我引以为豪的事情,但我仍在使用 .net 2 :)
        【解决方案7】:

        根据您的需要和配置,您可以使用 Windows 搜索索引:https://msdn.microsoft.com/en-us/library/windows/desktop/bb266517(v=vs.85).aspx

        根据您的配置,这可以大大提高性能。

        【讨论】:

          【解决方案8】:

          出于文件和目录搜索的目的,我想提供具有广泛搜索机会的多线程 .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>();
              }
          
              public void Startsearch()
              {
                  CancellationTokenSource tokenSource = new CancellationTokenSource();
          
                  searcher = new FileSearcher(@"C:\", (f) =>
                  {
                      return Regex.IsMatch(f.Name, @".*[Dd]ragon.*.jpg$");
                  }, tokenSource);  
          
          
                  searcher.FilesFound += (sender, arg) => 
                  {
                      lock (locker) // using a lock is obligatorily
                      {
                          arg.Files.ForEach((f) =>
                          {
                              files.Add(f);
                              Console.WriteLine($"File location: {f.FullName}, \nCreation.Time: {f.CreationTime}");
                          });
          
                          if (files.Count >= 10) 
                              searcher.StopSearch();
                      }
                  };
          
                  searcher.SearchCompleted += (sender, arg) => 
                  {
                      if (arg.IsCanceled) 
                          Console.WriteLine("Search stopped.");
                      else
                          Console.WriteLine("Search completed.");
          
                      Console.WriteLine($"Quantity of files: {files.Count}"); 
                  };
          
                  searcher.StartSearchAsync();
              }
          }
          

          这是其他示例的一部分:

          ***
          List<string> folders = new List<string>
          {
            @"C:\Users\Public",
            @"C:\Windows\System32",
            @"D:\Program Files",
            @"D:\Program Files (x86)"
          }; // list of search directories
          
          List<string> keywords = new List<string> { "word1", "word2", "word3" }; // list of search keywords
          
          FileSearcherMultiple multipleSearcher = new FileSearcherMultiple(folders, (f) =>
          {
            if (f.CreationTime >= new DateTime(2015, 3, 15) &&
               (f.Extension == ".cs" || f.Extension == ".sln"))
              foreach (var keyword in keywords)
                if (f.Name.Contains(keyword))
                  return true;
            return false;
          }, tokenSource, ExecuteHandlers.InCurrentTask, true);  
          ***
          

          而且可以使用简单的静态方法:

          List<FileInfo> files = FileSearcher.GetFilesFast(@"C:\Users", "*.xml");
          

          请注意,此库的所有方法都不会抛出 UnauthorizedAccessException 而不是标准的 .NET 搜索方法。

          如果您使用多核处理器,此库的其他快速方法的执行速度至少比简单的单线程递归算法快 2 倍。

          【讨论】:

            【解决方案9】:

            对于那些想要搜索单个文件并且知道根目录的人,我建议您尽可能保持简单。这种方法对我有用。

                private void btnSearch_Click(object sender, EventArgs e)
                {
                    string userinput = txtInput.Text;
            
                    string sourceFolder = @"C:\mytestDir\";
                    string searchWord = txtInput.Text + ".pdf";
                    string filePresentCK = sourceFolder + searchWord;
                    if (File.Exists(filePresentCK))
                        {
            
                            pdfViewer1.LoadFromFile(sourceFolder+searchWord);
                        }
                        else if(! File.Exists(filePresentCK))
                        {
                            MessageBox.Show("Unable to Find file :" + searchWord);
                        }
            
                    txtInput.Clear();
            
                }// end of btnSearch method 
            

            【讨论】:

              猜你喜欢
              • 2019-12-09
              • 2013-07-17
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-09-23
              • 2015-08-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多