有效地检索和过滤文件

时间:2009-02-12 13:19:42

标签: c# linq performance file

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

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

接受的答案是:

var files = Directory.GetFiles("C:\\path", "*.*", SearchOption.AllDirectories)
            .Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg"));

这让我感到非常低效。如果您在包含数千个文件的目录树(它使用SearchOption.AllDirectories)上进行搜索,则指定目录树中的每个文件都会加载到内存中,然后才会删除不匹配项。 (让我想起了ASP.NET数据网格提供的“分页”。)

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

可能只是我缺乏Linq知识,我提到的方式实际上效率低下吗?

其次,有没有更有效的方法可以使用和不使用Linq(不需要多次调用GetFiles)?

4 个答案:

答案 0 :(得分:2)

我分享了你的问题,我在excellent post的Matthew Podwysocki codebetter.com找到了解决方案。

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

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

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

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

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

答案 1 :(得分:1)

你对内存消耗是正确的。但是,我认为这是一个相当不成熟的优化。加载几千个字符串的数组完全没有问题,无论是性能还是内存消耗。然而,读取包含那么多文件的directoy, - 无论你如何存储/过滤文件名:它总是相对较慢。

答案 2 :(得分:1)

如何创建自己的目录遍历功能并使用C# yield operator

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

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

我知道你不能依赖GC.GetTotalMemory来进行内存分析,但是在我的所有测试运行中,显示的内存消耗都会少一些(100K)。

Runing time: 605ms
GC.GetTotalMemory(false): 3444684
Runing time: 577ms
GC.GetTotalMemory(false): 3293368

答案 3 :(得分:1)

GetFiles方法只读取文件名,而不是文件内容,因此在阅读所有名称时可能会浪费,我认为这不用担心。

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