代码之家  ›  专栏  ›  技术社区  ›  Ash

有效地检索和过滤文件

  •  3
  • Ash  · 技术社区  · 16 年前

    这个 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 回复  |  直到 16 年前
        1
  •  2
  •   Markus Olsson    15 年前

    我分享了你的问题,在Matthew Podwysocki的 excellent post 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变量将指向返回匹配文件(延迟执行样式)的枚举器。

        2
  •  1
  •   Konrad Rudolph    16 年前

    关于内存消耗,你是对的。然而,我认为这是一个相当过早的优化。加载几千个字符串的数组完全没有问题,无论是性能还是内存消耗都没有问题。但是,读取包含许多文件的Directoy时, _

        3
  •  1
  •   Rune Grimstad    16 年前

    getfiles方法只读取文件名,而不读取文件内容,因此在读取所有名称时可能会浪费资源,我不认为这是需要担心的。

    据我所知,唯一的选择是执行多个getfiles调用并将结果添加到集合中,但这会变得笨拙,需要您扫描文件夹几次,因此我怀疑速度也会变慢。

        4
  •  1
  •   Leandro López    16 年前

    创建自己的目录遍历函数并使用 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