Я пишу программу, которая должна искать каталог и все его подкаталоги для файлов, которые имеют определенное расширение. Это будет используемым и на локальном, и на сетевом диске, таким образом, производительность является чем-то вроде проблемы.
Вот рекурсивный метод, который я использую теперь:
private void GetFileList(string fileSearchPattern, string rootFolderPath, List<FileInfo> files)
{
DirectoryInfo di = new DirectoryInfo(rootFolderPath);
FileInfo[] fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
files.AddRange(fiArr);
DirectoryInfo[] diArr = di.GetDirectories();
foreach (DirectoryInfo info in diArr)
{
GetFileList(fileSearchPattern, info.FullName, files);
}
}
Я мог установить SearchOption на AllDirectories и не использовать рекурсивный метод, но в будущем я захочу ввести некоторый код для уведомления пользователя, какая папка в настоящее время сканируется.
В то время как я создаю список объектов FileInfo теперь все, о чем я действительно забочусь, пути к файлам. У меня будет существующий список файлов, которые я хочу сравнить с новым списком файлов для наблюдения, какие файлы были добавлены или удалены. Там какой-либо более быстрый путь состоит в том, чтобы генерировать этот список путей к файлам? Есть ли что-нибудь, что я могу сделать для оптимизации этого поиска файла вокруг запросов для файлов на общем сетевом диске?
Обновление 1
Я пытался создать нерекурсивный метод, который делает то же самое первым нахождением всех подкаталогов и затем многократно сканированием каждого каталога для файлов. Вот метод:
public static List<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);
List<DirectoryInfo> dirList = new List<DirectoryInfo>(rootDir.GetDirectories("*", SearchOption.AllDirectories));
dirList.Add(rootDir);
List<FileInfo> fileList = new List<FileInfo>();
foreach (DirectoryInfo dir in dirList)
{
fileList.AddRange(dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly));
}
return fileList;
}
Обновление 2
Хорошо, таким образом, я запустил некоторые тесты на локальном и удаленной папке, оба из которых имеют много файлов (~1200). Вот методы, на которых я запустил тесты. Результаты ниже.
Method Local Folder Remote Folder GetFileListA() 00:00.0781235 05:22.9000502 GetFileListB() 00:00.0624988 03:43.5425829 GetFileListC() 00:00.0624988 05:19.7282361 GetFileListD() 00:00.0468741 03:38.1208120 DirectoryInfo.GetFiles 00:00.0468741 03:45.4644210 Directory.GetFiles 00:00.0312494 03:48.0737459
.. .so похож на Marc, является самым быстрым.
Попробуйте эту версию блока итератора, которая избегает рекурсии, и объекты Info
:
public static IEnumerable<string> GetFileList(string fileSearchPattern, string rootFolderPath)
{
Queue<string> pending = new Queue<string>();
pending.Enqueue(rootFolderPath);
string[] tmp;
while (pending.Count > 0)
{
rootFolderPath = pending.Dequeue();
try
{
tmp = Directory.GetFiles(rootFolderPath, fileSearchPattern);
}
catch (UnauthorizedAccessException)
{
continue;
}
for (int i = 0; i < tmp.Length; i++)
{
yield return tmp[i];
}
tmp = Directory.GetDirectories(rootFolderPath);
for (int i = 0; i < tmp.Length; i++)
{
pending.Enqueue(tmp[i]);
}
}
}
Обратите внимание, что 4.0 имеет встроенные версии блока итератора ( EnumerateFiles
, EnumerateFileSystemEntries
), которые могут быть быстрее (более прямой доступ к файловой системе; меньше массивов)
Я был бы склонен вернуть IENumerableable <> в этом случае - в зависимости от того, как вы потребляете результаты, это может быть улучшение, плюс вы уменьшаете свой след параметра на 1 / 3 И избегайте непрерывно переходя на этот список.
private IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
DirectoryInfo di = new DirectoryInfo(rootFolderPath);
var fiArr = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
foreach (FileInfo fi in fiArr)
{
yield return fi;
}
var diArr = di.GetDirectories();
foreach (DirectoryInfo di in diArr)
{
var nextRound = GetFileList(fileSearchPattern, di.FullnName);
foreach (FileInfo fi in nextRound)
{
yield return fi;
}
}
yield break;
}
Другая идея будет выключена Фоновым механизмом
объектов к тролле через каталоги. Вы бы не хотели бы новую нить для каждого каталога, но вы можете создать их на верхнем уровне (первый проход через GetFileList ()
), поэтому, если вы выполняете на C: \
Драйв, с 12 каталогами, каждый из этих каталогов будет искать другой поток,которые затем будут рекурсировать через подкаталоги. У вас будет одна нить, проходя через C: \ Windows
, пока другой проходит через файлы программы C: \ Program
. Есть много переменных относительно того, как это собирается повлиять на производительность - вам придется проверить его, чтобы увидеть.
Вы можете использовать параллельный Foreach (.NET 4.0) или вы можете попробовать Parallel Faralel.Foreach ITERATOR для .NET3.5. Это может ускорить ваш поиск.
Рассмотрим расщепление обновленного метода на два итератора:
private static IEnumerable<DirectoryInfo> GetDirs(string rootFolderPath)
{
DirectoryInfo rootDir = new DirectoryInfo(rootFolderPath);
yield return rootDir;
foreach(DirectoryInfo di in rootDir.GetDirectories("*", SearchOption.AllDirectories));
{
yield return di;
}
yield break;
}
public static IEnumerable<FileInfo> GetFileList(string fileSearchPattern, string rootFolderPath)
{
var allDirs = GetDirs(rootFolderPath);
foreach(DirectoryInfo di in allDirs())
{
var files = di.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
foreach(FileInfo fi in files)
{
yield return fi;
}
}
yield break;
}
также, в дальнейшем к сетевому сценарию, если вы смогли установить небольшой сервис на этом сервере, в который вы могли бы позвонить в Клиентский автомат, вы получите гораздо ближе к результатам «локальной папки», потому что поиск может выполнить на на сервере и просто вернуть вам результаты. Это будет ваша самая большая скорость повышения скорости в сценарии сетевой папки, но может быть недоступна в вашей ситуации. Я использовал программу синхронизации файлов, которая включает в себя эту опцию - как только я установил услугу на моем сервере, программа стала Way , чтобы выявить файлы, которые были новыми, удалены и вне синхронизации. Отказ
Краткий ответ о том, как улучшить производительность этого кода: вы не можете.
Реальная производительность ударила вашего опыта - это фактическая задержка диска или сети, поэтому независимо от того, каким способом вы его переворачиваете, вы должны проверить и итерацию через каждый элемент файла и извлечения каталога и списках файлов. (Это, конечно, исключает модификации оборудования или драйверов для уменьшения или улучшения задержки диска, но многие люди уже заплатили много денег, чтобы решить эти проблемы, поэтому мы проигнорируем эту сторону этого)
Оригинальные ограничения существуют несколько решений, уже опубликованных, что более или менее элегантно обернут процесс итерации (однако, поскольку я предполагаю, что я читаю с одного жесткого диска, параллелизм не поможет быстрее поперечно попереживать дерево каталогов, а может Даже увеличить это время, поскольку у вас сейчас есть два или более потоков, сражающихся за данными на разных частях привода, поскольку она пытается обратиться за обратно и четвертым) уменьшить количество созданных объектов и т. Д. Однако, если мы оценим, как функция будет потреблена К концу разработчика есть некоторые оптимизации и обобщения, с которыми мы можем придумать.
Во-первых, мы можем задержать выполнение производительности, возвращая IEnumerable, возврат урожайности выполняет это путем компиляции в государственном автомате, внутри анонимного класса, который реализует iEnumerable и возвращается при выполнении метода. Большинство методов в LINQ записываются для задержки выполнения до тех пор, пока итерация не будет выполнена, поэтому код в выборе или Selectmany не будет выполнен, пока IEnumerable не будет передан. Конечный результат задержки выполнения ощущается только в том случае, если вам нужно предпринять подмножество данных позже, например, если вам нужны только первые 10 результатов, задерживая выполнение запроса, которое возвращает несколько тысяч результатов, не будет Итайте через все 1000 результатов, пока вам не нужно больше десяти.
Теперь, учитывая, что вы хотите выполнить поиск подпапок, я также могу сделать вывод, что может быть полезно, если вы можете указать эту глубину, и если я сделаю это, это также обобщает мою проблему, но также требует рекурсивного решения. Затем, позже, когда кто-то решит, что теперь он должен искать два каталогах глубоко, потому что мы увеличили количество файлов и решили добавить другой слой категоризации , вы можете просто сделать небольшую модификацию вместо того, чтобы повторно написать функция.
В свете всего этого, вот решение, которое я придумал, что обеспечивает более общее решение, чем некоторые другие выше:
public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, string rootFolderPath)
{
return BetterFileList(fileSearchPattern, new DirectoryInfo(rootFolderPath), 1);
}
public static IEnumerable<FileInfo> BetterFileList(string fileSearchPattern, DirectoryInfo directory, int depth)
{
return depth == 0
? directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly)
: directory.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly).Concat(
directory.GetDirectories().SelectMany(x => BetterFileList(fileSearchPattern, x, depth - 1)));
}
на боковой записке, что-то еще, что никто не упомянул. это разрешения и безопасность файлов. В настоящее время нет никаких проколочных запросов, обработчиков или разрешений, и код бросит исключения разрешений на файл, если он сталкивается с каталогом, у него нет доступа к итерации.
Классный вопрос.
Я немного поиграл и, используя блоки итераторов и LINQ, похоже, улучшил вашу пересмотренную реализацию примерно на 40%
Мне было бы интересно, чтобы вы протестировали ее, используя ваши методы тайминга и в вашей сети, чтобы посмотреть, как выглядит разница.
Вот в чем суть
private static IEnumerable<FileInfo> GetFileList(string searchPattern, string rootFolderPath)
{
var rootDir = new DirectoryInfo(rootFolderPath);
var dirList = rootDir.GetDirectories("*", SearchOption.AllDirectories);
return from directoriesWithFiles in ReturnFiles(dirList, searchPattern).SelectMany(files => files)
select directoriesWithFiles;
}
private static IEnumerable<FileInfo[]> ReturnFiles(DirectoryInfo[] dirList, string fileSearchPattern)
{
foreach (DirectoryInfo dir in dirList)
{
yield return dir.GetFiles(fileSearchPattern, SearchOption.TopDirectoryOnly);
}
}