Существует ли более быстрый путь, чем это для нахождения всех файлов в каталоге и всех подкаталогов?

Я пишу программу, которая должна искать каталог и все его подкаталоги для файлов, которые имеют определенное расширение. Это будет используемым и на локальном, и на сетевом диске, таким образом, производительность является чем-то вроде проблемы.

Вот рекурсивный метод, который я использую теперь:

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). Вот методы, на которых я запустил тесты. Результаты ниже.

  • GetFileListA (): нерекурсивное решение в обновлении выше. Я думаю, что это эквивалентно решению Jay.
  • GetFileListB (): Рекурсивный метод от исходного вопроса
  • GetFileListC (): Получает все каталоги со статическим Каталогом. GetDirectories () метод. Затем получает все пути к файлам со статическим Каталогом. GetFiles () метод. Заполняет и возвращает Список
  • GetFileListD (): решение Marc Gravell с помощью очереди и возвратов IEnumberable. Я заполнил Список с получающимся IEnumerable
    • DirectoryInfo. GetFiles: Никакой дополнительный метод не создается. Инстанцированный DirectoryInfo от пути корневой папки. Названное использование GetFiles SearchOption. AllDirectories
  • Каталог. GetFiles: Никакой дополнительный метод не создается. Названный статическим методом GetFiles использования использования Каталога SearchOption. AllDirectories
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, является самым быстрым.

36
задан cramopy 24 June 2015 в 13:39
поделиться

6 ответов

Попробуйте эту версию блока итератора, которая избегает рекурсии, и объекты 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 ), которые могут быть быстрее (более прямой доступ к файловой системе; меньше массивов)

45
ответ дан 27 November 2019 в 05:48
поделиться

Я был бы склонен вернуть 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 . Есть много переменных относительно того, как это собирается повлиять на производительность - вам придется проверить его, чтобы увидеть.

0
ответ дан 27 November 2019 в 05:48
поделиться

Вы можете использовать параллельный Foreach (.NET 4.0) или вы можете попробовать Parallel Faralel.Foreach ITERATOR для .NET3.5. Это может ускорить ваш поиск.

0
ответ дан 27 November 2019 в 05:48
поделиться

Рассмотрим расщепление обновленного метода на два итератора:

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 , чтобы выявить файлы, которые были новыми, удалены и вне синхронизации. Отказ

1
ответ дан 27 November 2019 в 05:48
поделиться

Краткий ответ о том, как улучшить производительность этого кода: вы не можете.

Реальная производительность ударила вашего опыта - это фактическая задержка диска или сети, поэтому независимо от того, каким способом вы его переворачиваете, вы должны проверить и итерацию через каждый элемент файла и извлечения каталога и списках файлов. (Это, конечно, исключает модификации оборудования или драйверов для уменьшения или улучшения задержки диска, но многие люди уже заплатили много денег, чтобы решить эти проблемы, поэтому мы проигнорируем эту сторону этого)

Оригинальные ограничения существуют несколько решений, уже опубликованных, что более или менее элегантно обернут процесс итерации (однако, поскольку я предполагаю, что я читаю с одного жесткого диска, параллелизм не поможет быстрее поперечно попереживать дерево каталогов, а может Даже увеличить это время, поскольку у вас сейчас есть два или более потоков, сражающихся за данными на разных частях привода, поскольку она пытается обратиться за обратно и четвертым) уменьшить количество созданных объектов и т. Д. Однако, если мы оценим, как функция будет потреблена К концу разработчика есть некоторые оптимизации и обобщения, с которыми мы можем придумать.

Во-первых, мы можем задержать выполнение производительности, возвращая 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)));
}

на боковой записке, что-то еще, что никто не упомянул. это разрешения и безопасность файлов. В настоящее время нет никаких проколочных запросов, обработчиков или разрешений, и код бросит исключения разрешений на файл, если он сталкивается с каталогом, у него нет доступа к итерации.

5
ответ дан 27 November 2019 в 05:48
поделиться

Классный вопрос.

Я немного поиграл и, используя блоки итераторов и 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);
    }
}
8
ответ дан 27 November 2019 в 05:48
поделиться
Другие вопросы по тегам:

Похожие вопросы: