【问题标题】:Searching Subdirectories in C#在 C# 中搜索子目录
【发布时间】:2010-12-27 10:06:07
【问题描述】:

我有一个文件名列表,我想搜索一个目录及其所有子目录。这些目录每个包含大约 200,000 个文件。我的代码找到了该文件,但每个文件大约需要 20 分钟。有人可以提出更好的方法吗?

代码片段

String[] file_names = File.ReadAllLines(@"C:\file.txt");
foreach(string file_name in file_names) 
{
    string[] files = Directory.GetFiles(@"I:\pax\", file_name + ".txt",
                                        SearchOption.AllDirectories);
    foreach(string file in files)
    {
        System.IO.File.Copy(file, 
                            @"C:\" + 
                            textBox1.Text + @"\N\O\" + 
                            file_name + 
                            ".txt"
                            );
    }

}

【问题讨论】:

    标签: c# .net performance search file-io


    【解决方案1】:

    如果您在同一目录结构中搜索多个文件,您应该在该目录结构中找到所有文件一次,然后在内存中搜索它们。无需一次又一次地进入文件系统。

    编辑:使用 LINQ 有一种优雅的方式来执行此操作 - 没有那么优雅的方式。这是 LINQ 方式:

    using System;
    using System.IO;
    using System.Linq;
    
    class Test
    {
        static void Main()
        {
            // This creates a lookup from filename to the set of 
            // directories containing that file
            var textFiles = 
                Directory.GetFiles("I:\\pax", "*.txt", SearchOption.AllDirectories)
                         .ToLookup(file => Path.GetFileName(file),
                                   file => Path.GetDirectoryName(file));
    
            string[] fileNames = File.ReadAllLines(@"c:\file.txt");
            // Remove the quotes for your real code :)
            string targetDirectory = "C:\\" + "textBox1.Text" + @"\\N\\O\\";
    
            foreach (string fileName in fileNames)
            {
                string tmp = fileName + ".txt";
                foreach (string directory in textFiles[tmp])
                {
                    string source = Path.Combine(directory, tmp);
                    string target = Path.Combine(targetDirectory, tmp);
                    File.Copy(source, target);                                       
                }
            }
        }
    }
    

    如果您需要非 LINQ 方式,请告诉我。不过,在我这样做之前要检查一件事 - 这可能会在彼此的顶部复制多个文件。这真的是你想要做的吗? (假设a.txt 存在于多个位置,而“a”在文件中。)

    【讨论】:

    • 哇。现在我明白为什么 Jon Skeet 会得到所有分数 - 他回答得最快!
    • 我必须看一个示例代码才能理解你的意思,我是新手才能从纯文本中理解结构
    • 几乎我的文件列表可能有 2000 个文件长
    • 乔恩一如既往的好答案。你认为像这些脚本语言这样的任务做得更好吗?认识一些使用 perl/python 完成此类任务的人。
    • 我们有一个适合 perl 的结构,我只是想组合一个封装 GUI 的进程
    【解决方案2】:

    您最好尝试将所有文​​件路径加载到内存中。调用 Directory.GetFiles() 一次,并将结果放入 HashSet<String>。然后在 HashSet 上进行查找。如果您有足够的内存,这将正常工作。这很容易尝试。

    如果你的内存用完了,你就必须更聪明,比如使用缓冲区缓存。最简单的方法是将所有文件路径作为行加载到数据库表中,并让查询处理器为您管理缓冲区缓存。

    这是第一个的代码:

    String[] file_names = File.ReadAllLines(@"C;\file.txt");
    HashSet<string> allFiles = new HashSet<string>();
    string[] files = Directory.GetFiles(@"I:\pax\", file_name + ".txt", SearchOption.AllDirectories);
    foreach (string file in files)
    {
        allFiles.Add(file);
    }
    
    foreach(string file_name in file_names)
    {
        String file = allFiles.FirstOrDefault(f => f == file_name);
        if (file != null)
        {
            System.IO.File.Copy(file, @"C:\" + textBox1.Text + @"\N\O\" + file_name + ".txt");
        }
    }
    

    您可以通过一次遍历一个目录并将生成的文件数组添加到哈希集来更智能地使用内存。这样所有文件名都必须存在于一个大的 String[] 中。

    【讨论】:

    • 每个文件!?我觉得这很难相信......你确定你把“Directory.GetFiles()”调用移出了循环的out吗?
    【解决方案3】:

    您一遍又一遍地执行递归 GetFiles(),这可能是最昂贵的部分。

    尝试将所有文​​件加载到内存中,然后进行自己的匹配。

    请注意,一次加载 1 个文件夹会更有效,然后搜索所有 file_name in file_names,然后对下一个文件夹重复此操作。

    【讨论】:

      【解决方案4】:

      扫描目录结构是一项 IO 密集型操作,无论您做什么,第一次 GetFiles() 调用都会占用大部分时间,到第一次调用结束时,大部分文件信息可能会在文件系统缓存中与第一次调用相比,第二次调用将立即返回(取决于您的可用内存和文件系统缓存大小)。

      您最好的选择可能是打开文件系统上的索引并以某种方式使用它; Querying the Index Programmatically

      【讨论】:

        【解决方案5】:

        乍一看,似乎有 .NET API 可以调用 Windows 索引服务……前提是您使用的机器启用了索引(我也不确定上述服务是否指的是 XP 时代索引服务或 Windows 搜索索引服务)。

        Google Search

        One possible lead

        Another

        【讨论】:

          【解决方案6】:

          尝试使用 LINQ 查询文件系统。不是 100% 确定性能,但它确实很容易测试。

          var filesResult = from file in new DirectoryInfo(path).GetFiles("*.txt", SearchOption.AllDirectories)
                            where file.Name = filename
                            select file;
          

          然后对结果做任何你想做的事情。

          【讨论】:

            【解决方案7】:

            Linq 答案可能会遇到问题,因为它会在开始从中选择之前将所有文件名加载到内存中。通常,您可能希望一次加载单个目录的内容,以减少内存压力。

            但是,对于这样的问题,您可能希望在问题表述中上一层。如果这是您经常执行的查询,那么您可以构建使用 FileSystemListener 来侦听顶级目录及其下方所有目录中的更改的东西。通过遍历所有目录并将它们构建到 Dictionary 或 HashSet 中来启动它。 (是的,这与 Linq 解决方案具有相同的内存问题)。然后,当您获得文件添加/删除/重命名修改时,更新字典。这样,每个单独的查询都可以很快得到答复。

            如果这是来自经常调用的工具的查询,您可能希望将 FileSystemWatcher 构建到服务中,并从需要知道的实际工具连接/查询该服务,以便文件系统信息可以构建一次,并在服务进程的生命周期内重复使用。

            【讨论】:

            • 哦,Windows 索引可能已经能够为您做到这一点——除非它不能保证是核心索引(事实上,它不是)。另一种加快速度的方法是转向 SSD。确实,旋转的磁性介质正在迅速走上恐龙的道路。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-10-07
            • 1970-01-01
            • 2011-02-03
            • 2010-12-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多