Эффективное получение и фильтрация файлов - PullRequest
3 голосов
/ 12 февраля 2009

Этот ранее SO вопрос говорит о том, как извлечь все файлы в дереве каталогов, которые соответствуют одному из нескольких расширений.

например. Получить все файлы в C: \ и все подкаталоги, соответствующие * .log, * .txt, * .dat.

Принятый ответ был таким:

var files = Directory.GetFiles("C:\\path", "*.*", SearchOption.AllDirectories)
            .Where(s => s.EndsWith(".mp3") || s.EndsWith(".jpg"));

Это кажется мне совершенно неэффективным. Если вы выполняли поиск в дереве каталогов, которое содержит тысячи файлов (оно использует SearchOption.AllDirectories), каждый отдельный файл в указанном дереве каталогов загружается в память, и только после этого удаляются несоответствия. (Напоминает мне о «пейджинге», предлагаемом сетями данных ASP.NET.)

К сожалению, стандартный метод System.IO.DirectoryInfo.GetFiles принимает только один фильтр за один раз.

Это может быть просто недостаток знания Linq, неужели это неэффективно, как я упоминал?

Во-вторых, есть ли более эффективный способ сделать это как с Linq, так и без него (без использования нескольких вызовов GetFiles)?

Ответы [ 4 ]

2 голосов
/ 05 марта 2009

Я поделился вашей проблемой и нашел решение в превосходном посте Мэтью Подвизоки на codebetter.com .

Он реализовал решение, используя нативные методы, которые позволяют вам предоставлять предикат в его реализацию GetFiles. Кроме того, он реализовал свое решение, используя операторы yield, эффективно сокращая использование памяти на файл до абсолютного минимума.

С его кодом вы можете написать что-то вроде следующего:

var allowedExtensions = new HashSet<string> { ".jpg", ".mp3" };

var files = GetFiles(
    "C:\\path", 
    SearchOption.AllDirectories, 
    fn => allowedExtensions.Contains(Path.GetExtension(fn))
);

И переменная files будет указывать на перечислитель, который возвращает сопоставленные файлы (стиль отложенного выполнения).

1 голос
/ 12 февраля 2009

Как насчет создания собственной функции обхода каталога и использования оператора C # yield ?

РЕДАКТИРОВАТЬ: я сделал простой тест, я не знаю, если это именно то, что вам нужно.

class Program
{
    static string PATH = "F:\\users\\llopez\\media\\photos";

    static Func<string, bool> WHERE = s => s.EndsWith(".CR2") || s.EndsWith(".html");

    static void Main(string[] args)
    {
        using (new Profiler())
        {
            var accepted = Directory.GetFiles(PATH, "*.*", SearchOption.AllDirectories)
                .Where(WHERE);

            foreach (string f in accepted) { }
        }

        using (new Profiler())
        {
            var files = traverse(PATH, WHERE);

            foreach (string f in files) { }
        }

        Console.ReadLine();
    }

    static IEnumerable<string> traverse(string path, Func<string, bool> filter)
    {
        foreach (string f in Directory.GetFiles(path).Where(filter))
        {
            yield return f;
        }

        foreach (string d in Directory.GetDirectories(path))
        {
            foreach (string f in traverse(d, filter))
            {
                yield return f;
            }
        }
    }
}

class Profiler : IDisposable
{
    private Stopwatch stopwatch;

    public Profiler()
    {
        this.stopwatch = new Stopwatch();
        this.stopwatch.Start();
    }

    public void Dispose()
    {
        stopwatch.Stop();
        Console.WriteLine("Runing time: {0}ms", this.stopwatch.ElapsedMilliseconds);
        Console.WriteLine("GC.GetTotalMemory(false): {0}", GC.GetTotalMemory(false));
    }
}

Я знаю, что вы не можете слишком полагаться на GC.GetTotalMemory для профилирования памяти, но во всех моих тестовых прогонах показывается немного меньшее потребление памяти (около 100К).

Runing time: 605ms
GC.GetTotalMemory(false): 3444684
Runing time: 577ms
GC.GetTotalMemory(false): 3293368
1 голос
/ 12 февраля 2009

Метод GetFiles читает только имена файлов, а не их содержимое, поэтому чтение всех имен может быть расточительным, и я не думаю, что об этом стоит беспокоиться.

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

1 голос
/ 12 февраля 2009

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

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