在C#中搜索子目录

时间:2009-12-16 20:53:44

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

我有一个文件名列表,我想搜索一个目录及其所有子目录。这些目录每个包含大约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"
                            );
    }

}

7 个答案:

答案 0 :(得分:13)

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

编辑:有一种优雅的方式,使用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”在文件中。)

答案 1 :(得分: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");
    }
}

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

答案 2 :(得分:1)

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

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

请注意,一次加载1个文件夹并为所有file_name in file_names搜索该文件夹会更有效,并为下一个文件夹重复该文件夹。

答案 3 :(得分:1)

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

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

答案 4 :(得分:0)

看起来似乎有.NET API来调用Windows索引服务...如果你正在使用的机器启用了索引(我也不确定上述服务是否指的是XP时代索引服务或Windows搜索索引服务)。

Google Search

One possible lead

Another

答案 5 :(得分:0)

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

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

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

答案 6 :(得分:0)

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

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

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