C# 高效地检索和筛选文件

C# 高效地检索和筛选文件,c#,linq,performance,file,C#,Linq,Performance,File,本文讨论如何检索目录树中与多个扩展名之一匹配的所有文件 例如,检索C:\和所有子目录中的所有文件,匹配*.log、*.txt、*.dat 公认的答案是: var files = Directory.GetFiles("C:\\path", "*.*", SearchOption.AllDirectories) .Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg")); 这让我觉得效率很低。如果在包含数千个文件的目录

本文讨论如何检索目录树中与多个扩展名之一匹配的所有文件

例如,检索C:\和所有子目录中的所有文件,匹配*.log、*.txt、*.dat

公认的答案是:

var files = Directory.GetFiles("C:\\path", "*.*", SearchOption.AllDirectories)
            .Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg"));
这让我觉得效率很低。如果在包含数千个文件的目录树上搜索(它使用SearchOption.AllDirectory),则指定目录树中的每个文件都会加载到内存中,只有在该目录树中,不匹配才会被删除。(让我想起了ASP.NET datagrids提供的“分页”功能。)

不幸的是,标准的System.IO.DirectoryInfo.GetFiles方法一次只接受一个筛选器

这可能是因为我缺乏Linq知识,我提到的方式是否真的效率低下


其次,有没有一种更有效的方法可以在使用Linq和不使用Linq的情况下(无需多次调用GetFiles)执行此操作?

关于内存消耗,您是对的。然而,我认为这是一个相当不成熟的优化。加载几千个字符串的数组根本没有问题,无论是性能还是内存消耗都没有问题。然而,读取包含这么多文件的directoy是一件非常困难的事情,不管您如何存储/过滤文件名:它总是相对缓慢。

创建自己的目录遍历函数并使用

编辑:我做了一个简单的测试,我不知道它是否正是你需要的

class Program
{
    static string PATH = "F:\\users\\llopez\\media\\photos";

    static Func<string, bool> WHERE = s => s.EndsWith(".CR2") || s.EndsWith(".html");

    static void Main(string[] args)
    {
        using (new Profiler())
        {
            var accepted = Directory.GetFiles(PATH, "*.*", SearchOption.AllDirectories)
                .Where(WHERE);

            foreach (string f in accepted) { }
        }

        using (new Profiler())
        {
            var files = traverse(PATH, WHERE);

            foreach (string f in files) { }
        }

        Console.ReadLine();
    }

    static IEnumerable<string> traverse(string path, Func<string, bool> filter)
    {
        foreach (string f in Directory.GetFiles(path).Where(filter))
        {
            yield return f;
        }

        foreach (string d in Directory.GetDirectories(path))
        {
            foreach (string f in traverse(d, filter))
            {
                yield return f;
            }
        }
    }
}

class Profiler : IDisposable
{
    private Stopwatch stopwatch;

    public Profiler()
    {
        this.stopwatch = new Stopwatch();
        this.stopwatch.Start();
    }

    public void Dispose()
    {
        stopwatch.Stop();
        Console.WriteLine("Runing time: {0}ms", this.stopwatch.ElapsedMilliseconds);
        Console.WriteLine("GC.GetTotalMemory(false): {0}", GC.GetTotalMemory(false));
    }
}

GetFiles方法只读取文件名,而不读取文件内容,因此,虽然读取所有名称可能是浪费,但我不认为这有什么好担心的


据我所知,唯一的替代方法是执行多个GetFiles调用并将结果添加到集合中,但这会变得很笨拙,需要您扫描文件夹几次,因此我怀疑它也会变慢

我分享了你的问题,并在Matthew Podwysocki的at中找到了解决方案

他使用本机方法实现了一个解决方案,该方法允许您在他的GetFiles实现中提供谓词。此外,他使用yield语句实现了他的解决方案,有效地将每个文件的内存利用率降至绝对最低

使用他的代码,您可以编写如下内容:

var allowedExtensions = new HashSet<string> { ".jpg", ".mp3" };

var files = GetFiles(
    "C:\\path", 
    SearchOption.AllDirectories, 
    fn => allowedExtensions.Contains(Path.GetExtension(fn))
);
var allowedExtensions=newhashset{.jpg',.mp3};
var files=GetFiles(
“C:\\path”,
SearchOption.AllDirectories,
fn=>allowedExtensions.Contains(Path.GetExtension(fn))
);

files变量将指向一个枚举器,该枚举器返回匹配的文件(延迟执行样式)。

对于大量文件,理想的做法是GetFiles()接受多个过滤器,然后逐个文件遍历整个目录树(无论如何都是这样),为每个匹配调用提供的方法。好的方面。我认为这可能会帮助你避免加载所有的文件名,并且只在需要时检索这些值。
var allowedExtensions = new HashSet<string> { ".jpg", ".mp3" };

var files = GetFiles(
    "C:\\path", 
    SearchOption.AllDirectories, 
    fn => allowedExtensions.Contains(Path.GetExtension(fn))
);