Поиск подкаталогов в C # - PullRequest
5 голосов
/ 16 декабря 2009

У меня есть список имен файлов, и я хочу найти каталог и все его подкаталоги. Эти каталоги содержат около 200 000 файлов каждый. Мой код находит файл, но это занимает около 20 минут на файл. Может кто-нибудь предложить лучший метод?

Фрагмент кода

String[] file_names = File.ReadAllLines(@"C:\file.txt");
foreach(string file_name in file_names) 
{
    string[] files = Directory.GetFiles(@"I:\pax\", file_name + ".txt",
                                        SearchOption.AllDirectories);
    foreach(string file in files)
    {
        System.IO.File.Copy(file, 
                            @"C:\" + 
                            textBox1.Text + @"\N\O\" + 
                            file_name + 
                            ".txt"
                            );
    }

}

Ответы [ 7 ]

13 голосов
/ 16 декабря 2009

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

РЕДАКТИРОВАТЬ: есть элегантный способ сделать это с LINQ - и менее элегантный способ, без. Вот способ LINQ:

using System;
using System.IO;
using System.Linq;

class Test
{
    static void Main()
    {
        // This creates a lookup from filename to the set of 
        // directories containing that file
        var textFiles = 
            Directory.GetFiles("I:\\pax", "*.txt", SearchOption.AllDirectories)
                     .ToLookup(file => Path.GetFileName(file),
                               file => Path.GetDirectoryName(file));

        string[] fileNames = File.ReadAllLines(@"c:\file.txt");
        // Remove the quotes for your real code :)
        string targetDirectory = "C:\\" + "textBox1.Text" + @"\\N\\O\\";

        foreach (string fileName in fileNames)
        {
            string tmp = fileName + ".txt";
            foreach (string directory in textFiles[tmp])
            {
                string source = Path.Combine(directory, tmp);
                string target = Path.Combine(targetDirectory, tmp);
                File.Copy(source, target);                                       
            }
        }
    }
}

Дайте мне знать, если вам нужен способ без LINQ. Одна вещь, которую нужно проверить, прежде чем я это сделаю, - это может скопировать несколько файлов поверх друг друга. Это действительно , что вы хотите сделать? (Представьте, что a.txt существует в нескольких местах, а "a" находится в файле.)

2 голосов
/ 16 декабря 2009

Возможно, вам лучше загрузить все пути к файлам в память. Вызовите Directory.GetFiles () один раз и поместите результаты в HashSet<String>. Затем выполните поиск по HashSet. Это будет хорошо работать, если у вас достаточно памяти. Было бы легко попробовать.

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

Вот код для первого:

String[] file_names = File.ReadAllLines(@"C;\file.txt");
HashSet<string> allFiles = new HashSet<string>();
string[] files = Directory.GetFiles(@"I:\pax\", file_name + ".txt", SearchOption.AllDirectories);
foreach (string file in files)
{
    allFiles.Add(file);
}

foreach(string file_name in file_names)
{
    String file = allFiles.FirstOrDefault(f => f == file_name);
    if (file != null)
    {
        System.IO.File.Copy(file, @"C:\" + textBox1.Text + @"\N\O\" + file_name + ".txt");
    }
}

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

1 голос
/ 17 декабря 2009

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

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

1 голос
/ 17 декабря 2009

Вы выполняете рекурсивный метод GetFiles () снова и снова, и это, вероятно, самая дорогая часть.

Попробуйте загрузить все файлы в память, и сделайте свое собственное сопоставление с этим.

Обратите внимание, что будет более эффективно загружать по 1 папке за раз, искать ее по всем file_name in file_names и повторять для следующей папки.

0 голосов
/ 17 декабря 2009

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

Однако для такой проблемы вы можете подняться на один уровень выше в формулировке проблемы. Если этот запрос вы делаете часто, то вы можете создать что-то, что использует FileSystemListener для прослушивания изменений в верхнем каталоге и во всех каталогах под ним. Запустите его при запуске, пройдясь по всем каталогам и встроив их в словарь <> или HashSet <>. (Да, это та же проблема с памятью, что и у решения Linq). Затем, когда вы получите файл добавления / удаления / переименования изменений, обновите словарь. Таким образом, на каждый отдельный запрос можно ответить очень быстро.

Если это запросы от инструмента, который часто вызывается, вы, вероятно, захотите встроить FileSystemWatcher в службу и подключиться к / запросить эту службу из фактического инструмента, который должен знать, чтобы информация о файловой системе могла создается один раз и повторно используется в течение всего жизненного цикла процесса обслуживания.

0 голосов
/ 17 декабря 2009

Попробуйте использовать LINQ для запроса файловой системы. Не на 100% уверен в производительности, но это действительно легко проверить.

var filesResult = from file in new DirectoryInfo(path).GetFiles("*.txt", SearchOption.AllDirectories)
                  where file.Name = filename
                  select file;

Тогда просто делай что хочешь с результатом.

0 голосов
/ 17 декабря 2009

На первый взгляд кажется, что есть .NET API для вызова службы индексирования Windows ... при условии, что на вашем компьютере включена индексация (и я также не уверен, относится ли вышеупомянутая служба к эпохе XP Служба индексирования или служба индексирования поиска Windows).

Поиск в Google

Один из возможных отведений

Другой

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