Ключом к ускорению такого межсетевого поиска является сокращение количества запросов по сети. Вместо того, чтобы получать все каталоги, а затем проверять каждый на наличие файлов, попробуйте получить все за один вызов.
В .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
экземпляров имеет недостаток, заключающийся в необходимости нескольких операций с файловой системой (я полагаю, что это в некоторой степени зависит от ОС), но для каждого имени любое решение должно знать, является ли он файлом или каталогом, так что это невозможно избежать в некоторых случаях. уровень (не прибегая к P / Invoke FindFileFirst
/ FindNextFile
/ FindClose
).
Помимо вышесказанного, было бы проще с методом расширения раздела:
Tuple<IEnumerable<T>,IEnumerable<T>> Extensions.Partition<T>(
this IEnumerable<T> input,
Func<T,bool> parition);
Писать, что быть ленивым, было бы интересным упражнением (потребляя входные данные только тогда, когда что-то перебирает один из выходных данных, в то время как буферизирует другой).