2009-02-12 54 views
3

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

回答

2

我分享了您的问题,并在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)) 
); 

并将文件变量将指向返回匹配的文件(延迟执行的风格)的枚举。

1

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

+0

对于数量巨大的文件,理想情况是GetFiles()接受多个过滤器,然后遍历整个目录树,逐个文件地遍历(无论如何),回调给每个匹配提供的方法。反正好点。 – Ash 2009-02-12 13:29:16

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
+0

我会研究它。 – Ash 2009-02-12 13:30:41

1

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

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