【问题标题】:Efficiently retrieving and filtering files高效检索和过滤文件
【发布时间】:2009-02-12 13:19:42
【问题描述】:

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)?

【问题讨论】:

    标签: c# linq performance file


    【解决方案1】:

    我分享了您的问题,并在 Matthew Podwysocki 的 excellent postcodebetter.com 中找到了解决方案。

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

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

    var allowedExtensions = new HashSet<string> { ".jpg", ".mp3" };
    
    var files = GetFiles(
        "C:\\path", 
        SearchOption.AllDirectories, 
        fn => allowedExtensions.Contains(Path.GetExtension(fn))
    );
    

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

    【讨论】:

      【解决方案2】:

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

      【讨论】:

      • 对于大量文件,理想的方法是 GetFiles() 接受多个过滤器,然后逐个文件遍历整个目录树(无论如何都必须这样做),为每个文件回调提供的方法匹配。无论如何都是好点。
      【解决方案3】:

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

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

      【讨论】:

        【解决方案4】:

        如何创建自己的目录遍历函数并使用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)左右。

        运行时间:605ms
        GC.GetTotalMemory(假):3444684
        运行时间:577ms
        GC.GetTotalMemory(false): 3293368

        【讨论】:

        • 我认为这可能会帮助您避免加载所有文件名并仅在需要时检索这些值。
        猜你喜欢
        • 2010-09-23
        • 2012-06-24
        • 2011-06-03
        • 1970-01-01
        • 2015-04-13
        • 1970-01-01
        • 2011-06-19
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多