Есть ли более быстрый способ найти все файлы в каталоге и всех подкаталогах? - PullRequest
35 голосов
/ 21 января 2010

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

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

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 () : нерекурсивное решение в обновлении выше. Я думаю, что это эквивалентно решению Джея.
  • GetFileListB () : рекурсивный метод из исходного вопроса
  • GetFileListC () : получает все каталоги с помощью статического метода Directory.GetDirectories () Затем получает все пути к файлам с помощью статического метода Directory.GetFiles (). Заполняет и возвращает список
  • GetFileListD () : решение Марка Гравелла использует очередь и возвращает IEnumberable. Я заполнил список полученным IEnumerable
    • DirectoryInfo.GetFiles : Дополнительный метод не создан. Создание DirectoryInfo из пути к корневой папке. Вызывается GetFiles с использованием SearchOption.AllDirectories
  • Directory.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

. , Похоже, что Марк самый быстрый.

Ответы [ 13 ]

0 голосов
/ 24 июня 2015

Это ужасно, и причина поиска файлов на платформах Windows ужасна, потому что MS допустила ошибку, которую они, похоже, не хотят исправлять. Вы должны быть в состоянии использовать SearchOption.AllDirectories И мы все вернули бы скорость, которую хотим. Но вы не можете этого сделать, потому что GetDirectories требуется обратный вызов, чтобы вы могли решить, что делать с каталогами, к которым у вас нет доступа. MS забыла или не думала тестировать класс на своих компьютерах.

Итак, мы все остаемся с бессмысленными рекурсивными циклами.

В C # / Managed C ++ у вас очень мало оприонов, это тоже варианты, которые использует MS, потому что их кодеры тоже не разобрались, как обойти это.

Главное, чтобы элементы отображения, такие как TreeViews и FileViews, выполняли только поиск и показывали то, что могут видеть пользователи. На контроле есть несколько помощников, включая триггеры, которые сообщают вам, когда вам нужно заполнить некоторые данные.

В деревьях, начиная с свернутого режима, ищите этот каталог, как и когда пользователь открывает его в дереве, что намного быстрее, чем ожидание заполнения всего дерева. То же самое в FileViews, я склонен к правилу 10%, сколько бы элементов не помещалось в области отображения, еще 10% готовы, если пользователь прокручивает, это приятно реагирует.

MS выполняет предварительный поиск и просмотр каталога. Небольшая база данных каталогов, файлов, это означает, что у вас на OnOpen ваши деревья и т. Д. Есть хорошая быстрая отправная точка, она немного падает при обновлении.

Но смешайте эти две идеи, возьмите ваши каталоги и файлы из базы данных, но выполните поиск обновлений, когда узел дерева расширен (только этот узел дерева) и как другой каталог выбран в дереве.

Но лучшее решение - добавить систему поиска файлов в качестве службы. У MS это уже есть, но, насколько мне известно, мы не получаем к нему доступа, я подозреваю, что это потому, что оно неуязвимо для ошибок «сбой доступа к каталогу». Как и в случае с MS, если у вас есть служба, работающая на уровне администратора, вы должны быть осторожны, чтобы не потерять свою безопасность только ради небольшой дополнительной скорости.

0 голосов
/ 21 января 2010

Вы можете использовать параллельный foreach (.Net 4.0) или попробовать Parallel.ForEach Iterator для бедного человека. для .Net3.5. Это может ускорить ваш поиск.

0 голосов
/ 21 января 2010

Я был бы склонен возвращать IEnumerable <> в этом случае - в зависимости от того, как вы потребляете результаты, это может быть улучшением, плюс вы уменьшаете размер своих параметров на 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;
}

Другая идея состоит в том, чтобы выделять BackgroundWorker объекты для перемещения по каталогам. Вам не нужно было бы создавать новый поток для каждого каталога, но вы можете создать их на верхнем уровне (первый проход через GetFileList()), поэтому, если вы выполняете на диске C:\ с 12 каталогами, каждый из этих каталогов будет искать в другой ветке, которая затем будет проходить через подкаталоги. У вас будет один поток, проходящий через C:\Windows, а другой - через C:\Program Files. Есть много переменных относительно того, как это повлияет на производительность - вам нужно проверить это, чтобы увидеть.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...