我写了一个实用程序,它将搜索系统中的所有固定驱动器以查找某些扩展名的文件。一些驱动器包含数百万个文件夹(例如,3000万个),文件可以在不同的深度(例如,第6个/第7个子文件夹)找到。在下面找到我正在使用的功能,
private void ReadDirectories(string targetDirectory)
{
IEnumerable<string> files = Directory.EnumerateFiles(targetDirectory).AsParallel();
ConcurrentBag<string> filesBag = new ConcurrentBag<string>(files);
Parallel.ForEach(filesBag, (file) =>
{
Interlocked.Increment(ref totalFileCount);
if (extension is a text/excel/word file )
{
try
{
// Some logic here
}
catch (AggregateException Aex)
{
Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
}
catch (Exception ex)
{
Log("File read failed: " + file + ex.Message + ex.StackTrace + ex.InnerException);
return; // This is break equivalent in Parallel.ForEach
}
}
});
IEnumerable<string> directories = Directory.EnumerateDirectories(targetDirectory).AsParallel();
ConcurrentBag<string> directoryBag = new ConcurrentBag<string>(directories);
Parallel.ForEach(directoryBag, (subDirectory) =>
{
try
{
ReadDirectories(subDirectory);
}
catch (AggregateException Aex)
{
Log("Aggregate exception thrown. " + Aex.Message + Aex.StackTrace + Aex.InnerException);
}
catch (UnauthorizedAccessException Uaex)
{
Log("Unauthorized exception: " + Uaex.Message + Uaex.StackTrace + Uaex.InnerException);
return;
}
catch (AccessViolationException Aex)
{
Log("Access violation exception: " + Aex.Message + Aex.StackTrace + Aex.InnerException);
return;
}
catch (Exception ex)
{
Log("Error while reading directories and files : " + ex.Message + ex.StackTrace + ex.InnerException);
return;
}
});
}
我面临的问题是,一旦应用程序开始枚举文件夹,物理内存消耗越来越多,并在一段时间后达到峰值(99%)。此时,不能执行任何其他活动。但我的应用程序内存大约在80 -90 MB之间运行。想知道物理内存使用率如此之高的原因,代码有问题吗?
考虑你的数字:3000万个文件夹,每个文件夹可能有一些文件留给你一些像文件和目录名称的1亿个字符串。并且由于该方法是递归的,所以袋子都保持到递归结束。
因此,如果平均文件/目录名称长度为100个字符,那么只有名称的RAM高达10GB。
正如其他人所解释的那样,存储这么多字符串会耗费大量内存而无法扩展。尝试并行枚举文件夹和文件也不会加快处理速度。
使用Directory.EnumerateFiles甚至更好,DirectoryInfo.EnumerateFiles与SearchOption.AllDirectories
更快地枚举当前文件夹和子文件夹中的所有文件并并行处理文件。
一个快速而又脏的选项是使用LINQ查询来过滤所有目标文件,使用Parallel.ForEach来处理文件,例如:
var extensions=new[]{".docx", ".xlsx",...};
var folder=new DirectoryInfo(targetDirectory);
var files=from file in folder.EnumerateFiles("*.*", SearchOption.AllDirectories)
where extensions.Contains(file.Extension,StringComparer.InvariantCultureIgnoreCase)
select file;
Parallel.ForEach(files,file=>ProcessFile(file));
这将使用与机器中的核心一样多的任务来处理文件。您可以通过指定不同的MaxDegreeOfParallelism选项来使用更多任务:
var options=new ParallelOptions { MaxDegreeOfParallelism = 4 }
Parallel.ForEach(files,options,ProcessFile);
Parallel.ForEach
将根据需要从files
查询中提取文件名。一旦EnumerateFiles
返回第一个结果,它就会开始处理,而不是等待所有文件名加载并缓存在内存中。