从包含大量文件的目录中检索文件

68

我有一个包含近1400万个以*.wav格式存储的音频样本的目录。

全部存储在同一级别,没有子目录。

当我使用DirectoryInfo.GetFiles()在该文件夹上循环遍历文件时,整个应用程序会冻结数分钟!

还有其他方法可以完成这个任务吗?也许每次读取1000个文件进行处理,然后再获取接下来的1000个文件,如此类推?


如果你正在使用网络SAN,DirectoryInfo.GetFiles()也是可怕的。它会锁定所有文件并阻止其他人访问最近创建的SAN文件。我们从未找到过非阻塞解决方案。 - SliverNinja - MSFT
如果你正在处理真正的性能关键点,我也建议考虑查看以下链接:https://dev59.com/w3RB5IYBdhLWcg3wFz8g#724184 - Sam Saffron
6个回答

95

您尝试过 DirectoryInfo 类的 EnumerateFiles 方法吗?

如 MSDN 所说:

EnumerateFilesGetFiles 方法的区别在于:当使用 EnumerateFiles 时,您可以在整个 FileInfo 对象集合返回之前开始枚举;而当您使用 GetFiles 时,必须等待整个 FileInfo 对象数组被返回后才能访问该数组。因此,当您处理许多文件和目录时,EnumerateFiles 可能更有效率。


我的 GetFiles 方法只返回字符串,而不是 FileInfo。 - MrFox
@MrFoxstring dir;Directory.GetFiles / Directory.EnumerateFiles 返回字符串new DirectoryInfo(dir).getFiles / new DirectoryInfo(dir).EnumerateFiles 返回 FileInfo - teamchong

47
在.NET 4.0中,Directory.EnumerateFiles(...)返回的是IEnumerable<string>类型(而不是Directory.GetFiles(...)返回的string[]),因此它可以流式传输文件而不需要缓存全部内容。
foreach(var file in Directory.EnumerateFiles(path)) {
    // ...
}

19

你遇到了Windows文件系统本身的限制。当一个目录中的文件数量增长到一定水平时(1400万已经远远超出这个阈值),访问该目录变得极其缓慢。无论你是一次读取一个文件还是1000个文件,都只是访问目录而已。

解决方法之一是创建子目录并将文件分组存储。如果每个目录中有1000-5000个文件(这只是猜测,但可以根据具体情况进行实验),那么打开/创建/删除文件时应该有良好的性能表现。

这就是为什么像Doxygen这样的应用程序,为每个类创建一个文件时,会遵循此方案并将所有内容放入两级随机命名的子目录中。


+1,完全正确。我想补充一点,最好使用数据库解决方案,或者使用适合大量文件的文件系统,比如ReiserFS。我不确定Windows是否有可用的ReiserFS驱动程序。 - Gleno
最好的例子是Git,它将对象放在以SHA1哈希的前两个字母命名的文件夹中。 - manojlds
@DXM - 你能提供一些关于这个限制的参考资料吗?我一直认为NTFS在处理大目录方面没有问题(http://technet.microsoft.com/en-us/library/cc781134(WS.10).aspx谈到了一个文件夹中有300k个文件),但是资源管理器是主要的瓶颈。 - ligos
@ligos - 不要改动,按原样来。我从事数字监控录像工作。我们有很多客户和大量的数据(最大的一次是1.5EB)。一段时间前,一个客户注意到磁盘性能指标不符合预期(他们花了大笔钱购买硬件),在与微软和硬件供应商开启支持案例后,微软代表告诉我们需要限制每个目录下的文件数量(我们曾经也是把所有文件都丢进一个文件夹中)。 - DXM
1
@DXM - 他们建议你限制的数字是多少?像你在帖子中建议的那样少于5k吗? - ligos

8

使用Win32 Api FindFile函数可以在不阻塞应用程序的情况下完成操作。

您还可以在System.Threading.Task (TPL)中调用Directory.GetFiles,以防止UI冻结。


5

享受。

    public List<string> LoadPathToAllFiles(string pathToFolder, int numberOfFilesToReturn)
    {
        var dirInfo = new DirectoryInfo(pathToFolder);
        var firstFiles = dirInfo.EnumerateFiles().Take(numberOfFilesToReturn).ToList();
        return firstFiles.Select(l => l.FullName).ToList();
    }

这将始终返回前 {numberOfFilesToReturn} 个文件... -1。 - markzzz

3

我经常遇到访问单个目录中大文件的问题。子目录是一个不错的选择,但有时它们也无法提供太多帮助。现在我所做的是创建一个索引文件——一个包含目录中所有文件名称的文本文件(前提是我正在创建该目录中的文件)。然后,我会读取索引文件,并打开目录中的实际文件进行处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接