在C#中搜索子目录

5

我有一个文件名列表,想要在一个目录及其所有子目录中搜索。这些目录每个包含大约20万个文件。我的代码可以找到文件,但每个文件需要大约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"
                            );
    }

}
7个回答

14
如果您正在搜索相同目录结构中的多个文件,则应该找到该目录结构中的所有文件,然后在内存中搜索它们。不需要一遍又一遍地访问文件系统。
编辑:有一种优雅的方法可以使用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”存在于文件中。)


我的文件列表可能有2000个文件左右。 - user222427
@Perpetualcoder:不一定 - 在脚本语言中,您仍然需要将所有文件放入适当的数据结构中才能开始。在声明类等方面存在一些冗长,但也仅此而已。 - Jon Skeet
@Jon,嘿,它实际上告诉我一个拥有32GB内存的服务器内存不足了,嘿。 - user222427
@JonSkeet,你的帖子很棒!在这段代码中,如何仅搜索用户指定的sourceFolder并禁用搜索子目录?谢谢! - Brian McCarthy
@JonSkeet,我在这里找到了一些代码,可以仅搜索指定的目录:http://msdn.microsoft.com/en-us/library/ms143448.aspx - DirectoryInfo[] directories = di.GetDirectories(searchPattern, SearchOption.TopDirectoryOnly); FileInfo[] files = di.GetFiles(searchPattern, SearchOption.TopDirectoryOnly); - Brian McCarthy
显示剩余9条评论

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()”调用从循环中移出了吗? - codekaizen

1

你正在反复执行递归的GetFiles(),这可能是最耗费时间的部分。

尝试将所有文件加载到内存中,并在其上进行匹配。

请注意,一次加载一个文件夹并搜索其中所有file_name in file_names,然后重复执行下一个文件夹会更有效率。


1

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

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


0

尝试使用LINQ查询文件系统。虽然不确定性能如何,但测试非常容易。

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

然后,您可以根据结果进行任何操作。


0
一眼看上去,似乎有.NET API可以调用Windows索引服务...前提是你使用的机器已启用索引(我也不确定上述服务是指XP时代的索引服务还是Windows搜索索引服务)。

谷歌搜索

一个可能的线索

另一个


0

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

然而,对于这样的问题,您可能希望在问题表述中向上移动一个级别。如果这是您经常查询的内容,则可以构建使用FileSystemListener来监听顶级目录及其下面所有目录中的更改的东西。在启动时通过遍历所有目录并将它们构建成Dictionary<>或HashSet<>来启动它(是的,这与Linq解决方案具有相同的内存问题)。然后,当您获得文件添加/删除/重命名修改时,请更新字典。这样,每个单独的查询都可以非常快速地回答。

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


哦,而且Windows索引可能已经可以为您完成这项工作——除了它不能保证是一个内核索引(事实上,它确实不是)。 另一种加速的方法是转移到SSD。确实,旋转磁介质正在迅速消失。 - Jon Watte

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接