此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)?
答案 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调用并将结果添加到集合中,但这会变得笨拙,并且需要您多次扫描文件夹,所以我怀疑它也会变慢。