【问题标题】:Is there a faster way than this to find all the files in a directory and all sub directories?有没有比这更快的方法来查找目录和所有子目录中的所有文件?
【发布时间】:2011-01-07 14:11:21
【问题描述】:

我正在编写一个程序,它需要在一个目录及其所有子目录中搜索具有特定扩展名的文件。这将在本地和网络驱动器上使用,因此性能有点问题。

这是我现在使用的递归方法:

private void GetFileList(string fileSearchPattern, string rootFolderPath, List<FileInfo> files)
{
    DirectoryInfo di = new DirectoryInfo(rootFolderPath);

    FileInfo[] fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
    files.AddRange(fiArr);

    DirectoryInfo[] diArr = di.GetDirectories();

    foreach (DirectoryInfo info in diArr)
    {
        GetFileList(fileSearchPattern, info.FullName, files);
    }
}

我可以将 SearchOption 设置为 AllDirectories 并且不使用递归方法,但将来我想插入一些代码来通知用户当前正在扫描哪个文件夹。

虽然我现在正在创建一个 FileInfo 对象列表,但我真正关心的是文件的路径。我将有一个现有的文件列表,我想将其与新的文件列表进行比较,以查看添加或删除了哪些文件。有没有更快的方法来生成这个文件路径列表?围绕查询共享网络驱动器上的文件,我可以做些什么来优化此文件搜索?


更新 1

我尝试通过首先查找所有子目录然后迭代扫描每个目录中的文件来创建一个非递归方法来执行相同的操作。方法如下:

public static List<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);

    List<DirectoryInfo> dirList = new List<DirectoryInfo>(rootDir.GetDirectories("*", SearchOption.AllDirectories));
    dirList.Add(rootDir);

    List<FileInfo> fileList = new List<FileInfo>();

    foreach (DirectoryInfo dir in dirList)
    {
        fileList.AddRange(dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly));
    }

    return fileList;
}

更新 2

好的,所以我在本地和远程文件夹上运行了一些测试,这两个文件夹都有很多文件(~1200)。这是我运行测试的方法。结果如下。

  • GetFileListA():上述更新中的非递归解决方案。我认为这相当于杰的解决方案。
  • GetFileListB():原始问题的递归方法
  • GetFileListC():使用静态 Directory.GetDirectories() 方法获取所有目录。然后使用静态 Directory.GetFiles() 方法获取所有文件路径。填充并返回一个列表
  • GetFileListD():Marc Gravell 使用队列并返回 IEnumberable 的解决方案。我用生成的 IEnumerable 填充了一个列表
    • DirectoryInfo.GetFiles:未创建其他方法。从根文件夹路径实例化一个 DirectoryInfo。使用 SearchOption.AllDirectories 调用 GetFiles
  • Directory.GetFiles:未创建其他方法。使用 SearchOption.AllDirectories 调用 Directory 的静态 GetFiles 方法
Method                       Local Folder       Remote Folder
GetFileListA()               00:00.0781235      05:22.9000502
GetFileListB()               00:00.0624988      03:43.5425829
GetFileListC()               00:00.0624988      05:19.7282361
GetFileListD()               00:00.0468741      03:38.1208120
DirectoryInfo.GetFiles       00:00.0468741      03:45.4644210
Directory.GetFiles           00:00.0312494      03:48.0737459

。 . .so 看起来 Marc 的速度最快。

【问题讨论】:

  • 对我来说看起来不错,我很想知道是否有其他方法。
  • 我想知道创建 FileInfo 对象是否实际上会花费任何时间从文件中读取任何内容,或者它是否只是知道它所代表的文件的路径并且仅在我请求时才查询实际文件来自其属性之一的其他信息,例如大小或修改日期。
  • 一个快速测试,AllDirectories 更快,但不是很多。 @Eric,我认为它只读取文件元数据,但我可能错了。
  • 我认为并行编程将是最好的解决方案。你可以使用 P-LINQ。

标签: c# .net file-io directory


【解决方案1】:

试试这个避免递归和Info对象的迭代器块版本:

public static IEnumerable<string> GetFileList(string fileSearchPattern, string rootFolderPath)
{
    Queue<string> pending = new Queue<string>();
    pending.Enqueue(rootFolderPath);
    string[] tmp;
    while (pending.Count > 0)
    {
        rootFolderPath = pending.Dequeue();
        try
        {
            tmp = Directory.GetFiles(rootFolderPath, fileSearchPattern);
        }
        catch (UnauthorizedAccessException)
        {
            continue;
        }
        for (int i = 0; i < tmp.Length; i++)
        {
            yield return tmp[i];
        }
        tmp = Directory.GetDirectories(rootFolderPath);
        for (int i = 0; i < tmp.Length; i++)
        {
            pending.Enqueue(tmp[i]);
        }
    }
}

另请注意,4.0 具有可能更快的内置迭代器块版本(EnumerateFilesEnumerateFileSystemEntries)(更直接地访问文件系统;更少的数组)

【讨论】:

  • 这个收益/回报的东西对我来说是新的。如果我将您的方法的结果保存到 IEnumerable 变量中,并在该变量上运行多个 ForEach 迭代,每个 ForEach 是否会耗尽所有值的一些缓存,或者每个 ForEach 是否会重新运行 GetFileList()?
  • @Eric - 它将重新运行它,但在那种情况下,使用.ToList() (LINQ) 或新的List&lt;string&gt;(GetFileList(...)) 缓冲它
  • 我认为 yield 延迟或推迟执行以供以后使用,例如当您使用 ToList() 时,或打印所有...仅当您正在寻找第一个结果时才有用或前面的东西,这样你就可以取消其余的执行。
  • EnumerateFiles的技巧,我试过了,因为中间抛出异常,无法使用。 (未经授权的访问)如果您搜索该问题,stackoverflow 上的某些人会提供围绕它的包装器。
  • @MarcGravell - 感谢这段代码。我冒昧地对其进行了修改,以便在访问某些目录时更可靠地处理UnauthorizedAccessException。如果您对我的更改不满意,请随时修改或回滚。
【解决方案2】:

很酷的问题。

我玩了一点,通过利用迭代器块和 LINQ,我似乎将您修改后的实现改进了大约 40%

我很想请您使用您的计时方法并在您的网络上对其进行测试,看看有什么不同。

这就是它的肉

private static IEnumerable<FileInfo> GetFileList(string searchPattern, string rootFolderPath)
{
    var rootDir = new DirectoryInfo(rootFolderPath);
    var dirList = rootDir.GetDirectories("*", SearchOption.AllDirectories);

    return from directoriesWithFiles in ReturnFiles(dirList, searchPattern).SelectMany(files => files)
           select directoriesWithFiles;
}

private static IEnumerable<FileInfo[]> ReturnFiles(DirectoryInfo[] dirList, string fileSearchPattern)
{
    foreach (DirectoryInfo dir in dirList)
    {
        yield return dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
    }
}

【讨论】:

  • Eric,别忘了在测试时,你必须遍历 IEnumerable 集合才能让这段代码真正执行。您至少应该将文件名回显到控制台以比较这两种方法,否则当迭代器方法返回如此之快但执行被延迟时,您将得到一个苹果和橘子的比较。
  • 只填充一个列表 怎么样? List myList = new List(GetFileLIst(some stuff));
  • Jay 是对的,当然你需要遍历集合才能看到性能命中。我故意省略了我如何回显到控制台来计算时间,假设您可能一直在做一些特定的事情或更精确地计算时间。这就是我所做的(对 .Count 的调用将导致遍历): var start = DateTime.Now; IEnumerable fileList = GetFileList("*.txt", @"C:\Temp"); var end = DateTime.Now; Console.WriteLine(String.Format("找到的文件:{0}", fileList.Count())); Console.WriteLine(String.Format("Time: {0}", end.Subtract(start).TotalMilliseconds));
  • 用完字符来回答您的评论 Eric。但是是的,将生成的 IEnum 放入 List 也会导致列表遍历
【解决方案3】:

如何提高该代码的性能的简短答案是:你不能。

真正影响您体验的是磁盘或网络的实际延迟,因此无论您以哪种方式翻转它,您都必须检查和迭代每个文件项并检索目录和文件列表。 (当然不包括硬件或驱动程序修改以减少或改善磁盘延迟,但很多人已经支付了很多钱来解决这些问题,所以我们暂时忽略它)

鉴于最初的限制,已经发布了几个解决方案,它们或多或少优雅地包装了迭代过程(但是,由于我假设我正在从单个硬盘驱动器读取,并行性无助于更快地遍历目录树,甚至可能会增加该时间,因为您现在有两个或多个线程在驱动器的不同部分争夺数据,因为它试图回溯和第四)减少创建的对象数量等。但是,如果我们评估如何最终开发人员将使用函数,我们可以提出一些优化和概括。

首先,我们可以通过返回 IEnumerable 来延迟性能的执行,yield return 通过在实现 IEnumerable 并在方法执行时返回的匿名类内部编译状态机枚举器来完成此操作。 LINQ 中的大多数方法都被写入延迟执行,直到执行迭代,因此 select 或 SelectMany 中的代码将不会执行,直到 IEnumerable 被迭代。延迟执行的最终结果只有在您需要稍后获取数据的子集时才会感觉到,例如,如果您只需要前 10 个结果,则延迟执行返回数千个结果的查询不会遍历整个 1000 个结果,直到需要超过 10 个。

现在,鉴于您想要进行子文件夹搜索,我还可以推断,如果您可以指定该深度,它可能会很有用,如果我这样做,它也会概括我的问题,但也需要递归解决方案。然后,稍后,当有人决定现在需要深入搜索 两个目录时,因为我们增加了文件数量并决定添加另一层分类,您可以简单地进行轻微修改而不是重新编写函数。

鉴于这一切,这是我提出的解决方案,它提供了比上述其他一些解决方案更通用的解决方案:

public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, string rootFolderPath)
{
    return BetterFileList(fileSearchPattern, new DirectoryInfo(rootFolderPath), 1);
}

public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, DirectoryInfo directory, int depth)
{
    return depth == 0
        ? directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly)
        : directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly).Concat(
            directory.GetDirectories().SelectMany(x => BetterFileList(fileSearchPattern, x, depth - 1)));
}

顺便说一句,到目前为止没有人提到的其他内容是文件权限和安全性。目前,没有检查、处理或权限请求,如果代码遇到无法遍历的目录,将抛出文件权限异常。

【讨论】:

    【解决方案4】:

    这需要 30 秒才能获得 200 万个符合过滤器的文件名。之所以这么快,是因为我只执行了 1 个枚举。每个额外的枚举都会影响性能。可变长度对您的解释是开放的,不一定与枚举示例相关。

    if (Directory.Exists(path))
    {
        files = Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories)
        .Where(s => s.EndsWith(".xml") || s.EndsWith(".csv"))
        .Select(s => s.Remove(0, length)).ToList(); // Remove the Dir info.
    }
    

    【讨论】:

    • “长度”在当前上下文中不存在。它应该包含什么?
    • 它只是用于删除路径的一部分并且可以解释。此处使用的示例是比较 2 个不同文件列表的前身,一个是本地的,一个在云中。
    • 我想知道如何在 where 子句中使用文件扩展名列表.. s.EndsWith(myListOfExtensions) 而不用硬编码单独添加它们。另外这将如何处理 UnathorizedAccessException ?
    【解决方案5】:

    可以说,BCL 方法是可移植的。如果保持 100% 的管理,我相信您能做的最好的事情就是在检查访问权限时调用 GetDirectories/Folders(或者可能不检查权限并在第一个线程花费太长时间时准备好另一个线程 - 这表明它是关于抛出 UnauthorizedAccess 异常——这可以通过使用 VB 或截至今天未发布的 c# 的异常过滤器来避免)。

    如果你想比 GetDirectories 更快,你必须调用 win32(findsomethingEx 等),它提供了允许在遍历 MFT 结构时忽略可能不必要的 IO 的特定标志。此外,如果驱动器是网络共享,则可以通过类似的方法大大提高速度,但这次也避免了过多的网络往返。

    现在,如果您有管理员并使用 ntfs,并且急于处理数百万个文件,那么通过它们的绝对最快方法(假设在磁盘延迟杀死的地方旋转生锈)是同时使用 mft 和日志结合起来,基本上将索引服务替换为针对您的特定需求的索引服务。如果您只需要查找文件名而不是大小(或大小,但您必须缓存它们并使用日志来通知更改),如果实施理想,这种方法实际上可以即时搜索数千万个文件和文件夹。可能有一两个付费软件对此感到困扰。周围有 C# 中的 MFT (DiscUtils) 和日志阅读 (google) 的示例。我只有大约 500 万个文件,仅使用 NTFSSearch 就足够了,因为搜索它们大约需要 10-20 秒。添加日记阅读后,该时间将降至

    【讨论】:

      【解决方案6】:

      DirectoryInfo 提供的信息似乎比您需要的多得多,请尝试通过管道传输 dir 命令并从中解析信息。

      【讨论】:

      • 你可以试试 Process.Start(cmd.exe, "dir c:\\ /s>allfiles.txt");然后解析 allfiles.txt
      【解决方案7】:

      我最近(2020 年)发现了这篇文章,因为需要计算慢速连接中的文件和目录,这是我能想到的最快的实现。 .NET 枚举方法(GetFiles()、GetDirectories())执行了大量的底层工作,相比之下大大降低了它们的速度。

      此解决方案不返回 FileInfo 对象,但可以对其进行修改 - 或者可能仅返回自定义 FileInfo 对象所需的相关数据。

      此解决方案利用 Win32 API 和 .NET 的 Parallel.ForEach() 来利用线程池来最大化性能。

      P/调用:

      /// <summary>
      /// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findfirstfilew
      /// </summary>
      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern IntPtr FindFirstFile(
          string lpFileName,
          ref WIN32_FIND_DATA lpFindFileData
          );
      
      /// <summary>
      /// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findnextfilew
      /// </summary>
      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern bool FindNextFile(
          IntPtr hFindFile,
          ref WIN32_FIND_DATA lpFindFileData
          );
      
      /// <summary>
      /// https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-findclose
      /// </summary>
      [DllImport("kernel32.dll", SetLastError = true)]
      public static extern bool FindClose(
          IntPtr hFindFile
          );
      

      方法:

      public static Tuple<long, long> CountFilesDirectories(
          string path,
          CancellationToken token
          )
      {
          if (String.IsNullOrWhiteSpace(path))
              throw new ArgumentNullException("path", "The provided path is NULL or empty.");
      
          // If the provided path doesn't end in a backslash, append one.
          if (path.Last() != '\\')
              path += '\\';
      
          IntPtr hFile = IntPtr.Zero;
          Win32.Kernel32.WIN32_FIND_DATA fd = new Win32.Kernel32.WIN32_FIND_DATA();
      
          long files = 0;
          long dirs = 0;
      
          try
          {
              hFile = Win32.Kernel32.FindFirstFile(
                  path + "*", // Discover all files/folders by ending a directory with "*", e.g. "X:\*".
                  ref fd
                  );
      
              // If we encounter an error, or there are no files/directories, we return no entries.
              if (hFile.ToInt64() == -1)
                  return Tuple.Create<long, long>(0, 0);
      
              //
              // Find (and count) each file/directory, then iterate through each directory in parallel to maximize performance.
              //
      
              List<string> directories = new List<string>();
      
              do
              {
                  // If a directory (and not a Reparse Point), and the name is not "." or ".." which exist as concepts in the file system,
                  // count the directory and add it to a list so we can iterate over it in parallel later on to maximize performance.
                  if ((fd.dwFileAttributes & FileAttributes.Directory) != 0 &&
                      (fd.dwFileAttributes & FileAttributes.ReparsePoint) == 0 &&
                      fd.cFileName != "." && fd.cFileName != "..")
                  {
                      directories.Add(System.IO.Path.Combine(path, fd.cFileName));
                      dirs++;
                  }
                  // Otherwise, if this is a file ("archive"), increment the file count.
                  else if ((fd.dwFileAttributes & FileAttributes.Archive) != 0)
                  {
                      files++;
                  }
              }
              while (Win32.Kernel32.FindNextFile(hFile, ref fd));
      
              // Iterate over each discovered directory in parallel to maximize file/directory counting performance,
              // calling itself recursively to traverse each directory completely.
              Parallel.ForEach(
                  directories,
                  new ParallelOptions()
                  {
                      CancellationToken = token
                  },
                  directory =>
                  {
                      var count = CountFilesDirectories(
                          directory,
                          token
                          );
      
                      lock (directories)
                      {
                          files += count.Item1;
                          dirs += count.Item2;
                      }
                  });
          }
          catch (Exception)
          {
              // Handle as desired.
          }
          finally
          {
              if (hFile.ToInt64() != 0)
                  Win32.Kernel32.FindClose(hFile);
          }
      
          return Tuple.Create<long, long>(files, dirs);
      }
      

      在我的本地系统上,GetFiles()/GetDirectories() 的性能可以接近此值,但在较慢的连接(VPN 等)上,我发现这要快得多——访问时间为 45 分钟和 90 秒约 40k 文件的远程目录,大小约 40 GB。

      这也可以很容易地修改以包含其他数据,例如计算的所有文件的总文件大小,或者从最远的分支开始快速递归并删除空目录。

      【讨论】:

        【解决方案8】:

        考虑将更新的方法分成两个迭代器:

        private static IEnumerable<DirectoryInfo> GetDirs(string rootFolderPath)
        {
             DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);
             yield return rootDir;
        
             foreach(DirectoryInfo di in rootDir.GetDirectories("*", SearchOption.AllDirectories));
             {
                  yield return di;
             }
             yield break;
        }
        
        public static IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
        {
             var allDirs = GetDirs(rootFolderPath);
             foreach(DirectoryInfo di in allDirs())
             {
                  var files = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
                  foreach(FileInfo fi in files)
                  {
                       yield return fi;
                  }
             }
             yield break;
        }
        

        此外,对于特定于网络的场景,如果您能够在该服务器上安装一个可以从客户端计算机调用的小型服务,那么您将更接近“本地文件夹”结果,因为搜索可以在服务器上执行 并将结果返回给您。这将是您在网络文件夹方案中最大的速度提升,但在您的情况下可能不可用。我一直在使用包含此选项的文件同步程序——在我的服务器上安装该服务后,该程序在识别新文件、已删除文件和已过期文件方面变得WAY更快——同步。

        【讨论】:

        • 知道不能在try catch中使用yield return,如何管理未经授权的访问异常?
        【解决方案9】:

        尝试并行编程:

        private string _fileSearchPattern;
        private List<string> _files;
        private object lockThis = new object();
        
        public List<string> GetFileList(string fileSearchPattern, string rootFolderPath)
        {
            _fileSearchPattern = fileSearchPattern;
            AddFileList(rootFolderPath);
            return _files;
        }
        
        private void AddFileList(string rootFolderPath)
        {
            var files = Directory.GetFiles(rootFolderPath, _fileSearchPattern);
            lock (lockThis)
            {
                _files.AddRange(files);
            }
        
            var directories = Directory.GetDirectories(rootFolderPath);
        
            Parallel.ForEach(directories, AddFileList); // same as Parallel.ForEach(directories, directory => AddFileList(directory));
        }
        

        【讨论】:

        • 使用ConcurrentBag&lt;T&gt;ConcurrentDictionary&lt;T,U&gt;代替手动locking等
        【解决方案10】:

        我遇到了同样的问题。这是我的尝试,它比调用 Directory.EnumerateFiles、Directory.EnumerateDirectories 或 Directory.EnumerateFileSystemEntries 递归要快得多:

        public static IEnumerable<string> EnumerateDirectoriesRecursive(string directoryPath)
        {
            return EnumerateFileSystemEntries(directoryPath).Where(e => e.isDirectory).Select(e => e.EntryPath);
        }
        
        public static IEnumerable<string> EnumerateFilesRecursive(string directoryPath)
        {
            return EnumerateFileSystemEntries(directoryPath).Where(e => !e.isDirectory).Select(e => e.EntryPath);
        }
        
        public static IEnumerable<(string EntryPath, bool isDirectory)> EnumerateFileSystemEntries(string directoryPath)
        {
            Stack<string> directoryStack = new Stack<string>(new[] { directoryPath });
        
            while (directoryStack.Any())
            {
                foreach (string fileSystemEntry in Directory.EnumerateFileSystemEntries(directoryStack.Pop()))
                {
                    bool isDirectory = (File.GetAttributes(fileSystemEntry) & (FileAttributes.Directory | FileAttributes.ReparsePoint)) == FileAttributes.Directory;
        
                    yield return (fileSystemEntry, isDirectory);
        
                    if (isDirectory)
                        directoryStack.Push(fileSystemEntry);
                }
            }
        }
        

        您可以修改代码以轻松搜索特定文件或目录。

        问候

        【讨论】:

          【解决方案11】:

          我需要从我的 C: 分区中获取所有文件,因此我结合了 Marc 和 Jaider 的答案并获得了没有递归和并行编程的函数,结果在 30 秒内处理了大约 370k 个文件。也许这会对某人有所帮助:

          void DirSearch(string path)
              {
                  ConcurrentQueue<string> pendingQueue = new ConcurrentQueue<string>();
                  pendingQueue.Enqueue(path);
          
                  ConcurrentBag<string> filesNames = new ConcurrentBag<string>();
                  while(pendingQueue.Count > 0)
                  {
                      try
                      {
                          pendingQueue.TryDequeue(out path);
          
                          var files = Directory.GetFiles(path);
          
                          Parallel.ForEach(files, x => filesNames.Add(x));
          
                          var directories = Directory.GetDirectories(path);
          
                          Parallel.ForEach(directories, (x) => pendingQueue.Enqueue(x));
                      }
                      catch (Exception)
                      {
                          continue;
                      }
                  }
              }
          

          【讨论】:

          • 我喜欢这个版本。您是否尝试了线程数来测试性能@hazaaa
          • @rolls 不是真的。我的小项目需要这个,所以我试图尽快完成它。您可以尝试并报告结果:)
          【解决方案12】:

          您可以使用并行 foreach (.Net 4.0),也可以尝试使用 Poor Man's Parallel.ForEach Iterator for .Net3.5。这可以加快您的搜索速度。

          【讨论】:

            【解决方案13】:

            这太可怕了,Windows 平台上的文件搜索工作之所以糟糕,是因为 MS 犯了一个错误,他们似乎不愿意纠正。你应该可以使用 SearchOption.AllDirectories 我们都会得到我们想要的速度。但是您不能这样做,因为 GetDirectories 需要回调,以便您可以决定如何处理您无权访问的目录。 MS 忘记或不想在自己的计算机上测试课程。

            所以,我们都留下了无意义的递归循环。

            在 C#/托管 C++ 中,您只有很少的选项,这些也是 MS 采用的选项,因为他们的编码人员也没有弄清楚如何绕过它。

            主要是显示项,例如 TreeViews 和 FileViews,只搜索和显示用户可以看到的内容。控件上有大量的助手,包括触发器,它们会告诉您何时需要填写一些数据。

            在树中,从折叠模式开始,当用户在树中打开它时搜索那个目录,这比等待一整棵树被填满要快得多。 在 FileViews 中也是如此,我倾向于 10% 的规则,无论有多少项目适合显示区域,如果用户滚动,还有另外 10% 准备好,它的响应性很好。

            MS 执行预搜索和目录监视。一个目录、文件的小数据库,这意味着你 OnOpen 你的树等有一个很好的快速起点,它在刷新时会下降一点。

            但是混合这两种想法,从数据库中获取目录和文件,但是在展开树节点(只是那个树节点)并在树中选择不同的目录时进行刷新搜索。

            但更好的解决方案是将文件搜索系统添加为服务。 MS 已经有了这个,但据我所知,我们无法访问它,我怀疑这是因为它不受“访问目录失败”错误的影响。 就像 MS 一样,如果您有一个在管理员级别运行的服务,您需要注意不要为了提高速度而放弃安全性。

            【讨论】:

              【解决方案14】:

              在这种情况下,我倾向于返回一个 IEnumerable —— 取决于您使用结果的方式,这可能是一种改进,另外您可以将参数占用空间减少 1/3 并避免传递该列表不断。

              private IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
              {
                  DirectoryInfo di = new DirectoryInfo(rootFolderPath);
              
                  var fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
                  foreach (FileInfo fi in fiArr)
                  {
                      yield return fi;
                  }
              
                  var diArr = di.GetDirectories();
              
                  foreach (DirectoryInfo di in diArr)
                  {
                      var nextRound = GetFileList(fileSearchPattern, di.FullnName);
                      foreach (FileInfo fi in nextRound)
                      {
                          yield return fi;
                      }
                  }
                  yield break;
              }
              

              另一个想法是剥离BackgroundWorker 对象以在目录中进行巨魔。您不希望每个目录都有一个新线程,但您可以在顶层创建它们(首先通过GetFileList()),所以如果您在C:\ 驱动器上执行,有12 个目录,每个目录都将由不同的线程搜索,然后将通过子目录递归。您将有一个线程通过C:\Windows,而另一个线程通过C:\Program Files。关于这将如何影响性能有很多变数 - 您必须对其进行测试才能看到。

              【讨论】:

                【解决方案15】:

                在 .net 核心中,您可以执行以下操作。它可以递归搜索所有子目录,性能良好,无需访问即可忽略路径。 我还尝试了

                中的其他方法

                https://www.codeproject.com/Articles/1383832/System-IO-Directory-Alternative-using-WinAPI

                public static IEnumerable<string> ListFiles(string baseDir)
                {
                    EnumerationOptions opt = new EnumerationOptions();
                    opt.RecurseSubdirectories = true;
                    opt.ReturnSpecialDirectories = false;
                    //opt.AttributesToSkip = FileAttributes.Hidden | FileAttributes.System;
                    opt.AttributesToSkip = 0;
                    opt.IgnoreInaccessible = true;
                
                    var tmp = Directory.EnumerateFileSystemEntries(baseDir, "*", opt);
                    return tmp;
                }
                

                【讨论】:

                  【解决方案16】:

                  出于文件和目录搜索的目的,我想提供具有广泛搜索机会的多线程 .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 part of the received files to the 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
                      }
                  }
                  

                  这是另一个例子:

                  ***
                  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); 
                  
                  ***
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2013-03-20
                    • 2016-11-07
                    • 1970-01-01
                    • 2019-06-21
                    • 2018-01-13
                    相关资源
                    最近更新 更多