Есть ли более эффективный способ перебора коллекции файлов и создания словаря содержимого файлов?

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

  1. Для данной папки переберите файлы в папке.
  2. В каждом файле пропустите первые четыре строки заголовка,
  3. После разделения строки на пробел, если результирующий массив содержит менее 7 элементов, пропустите его,
  4. Проверить, есть ли уже указанный элемент в словаре. Если это так, увеличьте счетчик. Если нет, добавьте его.

Это несложный процесс. Есть лучший способ сделать это? LINQ?

string sourceDirectory = @"d:\TESTDATA\";

string[] files = Directory.GetFiles(sourceDirectory, "*.log", 
    SearchOption.TopDirectoryOnly);

var dictionary = new Dictionary<string, int>();

foreach (var file in files)
{
    string[] lines = System.IO.File.ReadLines(file).Skip(4).ToArray();

    foreach (var line in lines)
    {
        var elements = line.Split(' ');

        if (elements.Length > 6)
        {
            if (dictionary.ContainsKey(elements[9]))
            {
                dictionary[elements[9]]++;
            }
            else
            {
                dictionary.Add(elements[9], 1);
            } 
        }
    }
}

person DenaliHardtail    schedule 26.04.2013    source источник
comment
Вероятно, принадлежит CodeReview.SE   -  person Corey Ogburn    schedule 26.04.2013
comment
Похоже, вы используете эти файлы как своего рода источник данных. В зависимости от их размера вы можете использовать текстовый драйвер ADO. Он позволяет использовать плоские файлы запросов почти как таблицы SQL. Это может быть немного сложно заставить его работать, но как только вы его правильно настроите, он действительно быстрый и простой в использовании.   -  person mroach    schedule 26.04.2013
comment
Вы делаете if (elements.Length > 6) и переходите к доступу elements[9], если это правда. Так что же будет, если длина будет 7, 8 или 9?   -  person Matthew Watson    schedule 26.04.2013


Ответы (4)


Что-то Linqy должен вам сделать. Сомневаюсь, что он более эффективен. И это почти наверняка больше хлопот для отладки. Но в наши дни это очень модно:

static Dictionary<string,int> Slurp( string rootDirectory )
{
  Dictionary<string,int> instance = Directory.EnumerateFiles(rootDirectory,"*.log",SearchOption.TopDirectoryOnly)
                                             .SelectMany( fn => File.ReadAllLines(fn)
                                                                    .Skip(4)
                                                                    .Select( txt => txt.Split( " ".ToCharArray() , StringSplitOptions.RemoveEmptyEntries) )
                                                                    .Where(x => x.Length > 9 )
                                                                    .Select( x => x[9])
                                                        )
                                             .GroupBy( x => x )
                                             .ToDictionary( x => x.Key , x => x.Count()) 
                                             ;
  return instance ;
}
person Nicholas Carey    schedule 26.04.2013

Более эффективный (с точки зрения производительности) способ сделать это - распараллелить внешний foreach с помощью Parallel.Foreach. Вам также понадобится объект ConcurrentDictionary вместо стандартного словаря.

person Tim P.    schedule 26.04.2013

Не уверен, ищете ли вы лучшую производительность или более элегантный код. Если вы предпочитаете функциональный стиль linq, возможно, что-то вроде этого:

var query= from element in
                       (
                           //go through all file names
                           from fileName in files
                           //read all lines from every file and skip first 4
                           from line in File.ReadAllLines(fileName).Skip(4)
                           //split every line into words
                           let lineData = line.Split(new[] {' '})
                           //select only lines with more than 6 words
                           where lineData.Count() > 6
                           //take 6th element from line
                           select lineData.ElementAt(6)
                       )
                   //outer query will group by element
                   group element by element
                   into g
                   select new
                       {
                           Key = g.Key,
                           Count = g.Count()
                       };
  var dictionary =  list.ToDictionary(e=>e.Key,e=>e.Count);

Результатом является словарь со словом в качестве ключа и количеством встречающихся слов в качестве значения.

person Jurica Smircic    schedule 26.04.2013

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

Если файлы могут стать большими (что кажется вероятным) и если ни одна строка не будет превышать 32 Кбайт (8 000-32 000 символов Unicode), я бы посоветовал вам читать их кусками размером около 32 Кбайт или 64 Кбайт (не символов). Чтение файла в байтах и ​​самостоятельное разделение на строки может быть быстрее, чем чтение его в виде строк, поскольку подразделение может происходить в другом потоке, чем при обращении к физическому диску.

Я бы предложил начать с одного потока для доступа к диску и одного потока для анализа и подсчета с очередью блокировки между ними. Поток доступа к диску должен помещать в очередь элементы данных, которые содержат массив байтов размером 32 КБ, указание количества допустимых байтов [может быть меньше 32 КБ в конце файла] и указатель того, является ли он последним запись файла. Поток синтаксического анализа должен прочитать эти элементы, разбить их на строки и обновить соответствующие счетчики.

Чтобы повысить эффективность подсчета, может быть полезно определить

class ExposedFieldHolder<T> {public T Value; }

а затем получить Dictionary<string, ExposedFieldHolder<int>>. Придется создать новый ExposedFieldHolder<int> для каждого слота словаря, но dictionary[elements[9]].Value++;, вероятно, будет быстрее, чем dictionary[elements[9]]++;, поскольку последний оператор переводится как dictionary[elements[9]] = dictionary[elements[9]]+1; и должен искать элемент один раз при чтении и еще раз при записи].

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

person supercat    schedule 26.04.2013