加快这种跨网络搜索的关键是减少跨网络的请求数量。与其获取所有目录,然后检查每个目录的文件,不如尝试一次调用获取所有内容。
在 .NET 3.5 中,没有一种方法可以递归地获取所有文件和文件夹,因此您必须自己构建它(见下文)。在 .NET 4 中,新的重载可以一步到位。
使用DirectoryInfo 还可以获取有关返回名称是文件还是目录的信息,这也减少了调用。
这意味着拆分所有目录和文件的列表变成这样:
struct AllDirectories {
public List<string> DirectoriesWithoutFiles { get; set; }
public List<string> DirectoriesWithFiles { get; set; }
}
static class FileSystemScanner {
public AllDirectories DivideDirectories(string startingPath) {
var startingDir = new DirectoryInfo(startingPath);
// allContent IList<FileSystemInfo>
var allContent = GetAllFileSystemObjects(startingDir);
var allFiles = allContent.Where(f => !(f.Attributes & FileAttributes.Directory))
.Cast<FileInfo>();
var dirs = allContent.Where(f => (f.Attributes & FileAttributes.Directory))
.Cast<DirectoryInfo>();
var allDirs = new SortedList<DirectoryInfo>(dirs, new FileSystemInfoComparer());
var res = new AllDirectories {
DirectoriesWithFiles = new List<string>()
};
foreach (var file in allFiles) {
var dirName = Path.GetDirectoryName(file.Name);
if (allDirs.Remove(dirName)) {
// Was removed, so first time this dir name seen.
res.DirectoriesWithFiles.Add(dirName);
}
}
// allDirs now just contains directories without files
res.DirectoriesWithoutFiles = new List<String>(addDirs.Select(d => d.Name));
}
class FileSystemInfoComparer : IComparer<FileSystemInfo> {
public int Compare(FileSystemInfo l, FileSystemInfo r) {
return String.Compare(l.Name, r.Name, StringComparison.OrdinalIgnoreCase);
}
}
}
实现 GetAllFileSystemObjects 取决于 .NET 版本。在 .NET 4 上这很容易:
ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
return root.GetFileSystemInfos("*.*", SearchOptions.AllDirectories);
}
在早期版本中还需要做更多的工作:
ILIst<FileSystemInfo> GetAllFileSystemObjects(DirectoryInfo root) {
var res = new List<FileSystemInfo>();
var pending = new Queue<DirectoryInfo>(new [] { root });
while (pending.Count > 0) {
var dir = pending.Dequeue();
var content = dir.GetFileSystemInfos();
res.AddRange(content);
foreach (var dir in content.Where(f => (f.Attributes & FileAttributes.Directory))
.Cast<DirectoryInfo>()) {
pending.Enqueue(dir);
}
}
return res;
}
这种方法尽可能少地调用文件系统,在 .NET 4 上只调用一次,或者在早期版本中每个目录调用一次,从而使网络客户端和服务器能够最大限度地减少底层文件系统调用和网络往返的次数。
获取FileSystemInfo 实例的缺点是需要多个文件系统操作(我相信这在一定程度上取决于操作系统),但是对于每个名称,任何解决方案都需要知道它是文件还是目录,因此在某些情况下这是无法避免的级别(不诉诸 FindFileFirst/FindNextFile/FindClose 的 P/Invoke)。
除此之外,使用分区扩展方法会更容易:
Tuple<IEnumerable<T>,IEnumerable<T>> Extensions.Partition<T>(
this IEnumerable<T> input,
Func<T,bool> parition);
把它写成懒惰是一个有趣的练习(只有在某些东西迭代一个输出时才消耗输入,同时缓冲另一个输出)。