2009-08-13 5 views
56

Я пытаюсь прочитать некоторые текстовые файлы, где каждая строка должна быть обработана. На данный момент я просто использую StreamReader, а затем каждый раз читаю каждую строку.Чтение строки строки по строке в C#

Мне интересно, есть ли более эффективный способ (с точки зрения LoC и читаемости) сделать это с помощью LINQ без ущерба для операционной эффективности. Примеры, которые я видел, включают загрузку всего файла в память, а затем его обработку. В этом случае, однако, я не считаю, что это было бы очень эффективно. В первом примере файлы могут получить примерно до 50 тыс., А во втором - не все строки файла должны быть прочитаны (размеры обычно < 10 тыс.).

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

Первый пример:

// Open file 
using(var file = System.IO.File.OpenText(_LstFilename)) 
{ 
    // Read file 
    while (!file.EndOfStream) 
    { 
     String line = file.ReadLine(); 

     // Ignore empty lines 
     if (line.Length > 0) 
     { 
      // Create addon 
      T addon = new T(); 
      addon.Load(line, _BaseDir); 

      // Add to collection 
      collection.Add(addon); 
     } 
    } 
} 

Второй пример:

// Open file 
using (var file = System.IO.File.OpenText(datFile)) 
{ 
    // Compile regexs 
    Regex nameRegex = new Regex("IDENTIFY (.*)"); 

    while (!file.EndOfStream) 
    { 
     String line = file.ReadLine(); 

     // Check name 
     Match m = nameRegex.Match(line); 
     if (m.Success) 
     { 
      _Name = m.Groups[1].Value; 

      // Remove me when other values are read 
      break; 
     } 
    } 
} 
+2

50K даже не достаточно большой, чтобы попасть в крупную кучу объекта. Потоковая передача имеет смысл, когда ваши файлы находятся в мегабайте (или большем) диапазоне, а не в килобайтах. –

ответ

91

Вы можете написать читателя линии LINQ на основе довольно легко с помощью итератора блок:

static IEnumerable<SomeType> ReadFrom(string file) { 
    string line; 
    using(var reader = File.OpenText(file)) { 
     while((line = reader.ReadLine()) != null) { 
      SomeType newRecord = /* parse line */ 
      yield return newRecord; 
     } 
    } 
} 

или сделать Джон счастлив:

static IEnumerable<string> ReadFrom(string file) { 
    string line; 
    using(var reader = File.OpenText(file)) { 
     while((line = reader.ReadLine()) != null) { 
      yield return line; 
     } 
    } 
} 
... 
var typedSequence = from line in ReadFrom(path) 
        let record = ParseLine(line) 
        where record.Active // for example 
        select record.Key; 

то есть ReadFrom(...) как лениво вычисляется последовательность без буферизации, идеально подходит для Where и т.д.

Заметим, что если вы используете OrderBy или стандартный GroupBy, он должен будет буфер данных в памяти; Если вам нужна группировка и агрегация, «PushLINQ» имеет некоторый причудливый код, позволяющий выполнять агрегацию данных, но отбрасывать его (без буферизации). Объяснение Джона is here.

+21

Ба, разделение проблем - отделите строку, читающую в отдельный итератор, и используйте нормальную проекцию :) –

+0

Много приятнее ... хотя и по-файловому;) –

+0

Не думаю, что ваши примеры будут компилироваться. «file» уже определен как параметр строки, поэтому вы не можете сделать это объявление в блоке использования. –

23

Проще всего читать строку и проверять, является ли она нулевой, чем для проверки EndOfStream все время.

Однако, у меня также есть LineReader класс в MiscUtil, что делает все это намного проще, - в основном это подвергает файл (или Func<TextReader> как IEnumerable<string>, который позволяет делать LINQ материал над ним Так что вы можете сделать что-то подобное. :

var query = from file in Directory.GetFiles("*.log") 
      from line in new LineReader(file) 
      where line.Length > 0 
      select new AddOn(line); // or whatever 

сердце LineReader это реализация IEnumerable<string>.GetEnumerator:

public IEnumerator<string> GetEnumerator() 
{ 
    using (TextReader reader = dataSource()) 
    { 
     string line; 
     while ((line = reader.ReadLine()) != null) 
     { 
      yield return line; 
     } 
    } 
} 

Почти все остальные источника просто дает гибкий способ с установки dataSource (что является Func<TextReader>).

+0

Как закрыть файл? И освободить ресурс? – ca9163d9

+0

@ dc7a9163d9: оператор 'using' делает это уже - вызов' dataSource() 'откроет файл, и поэтому он будет удален в конце инструкции' use'. –

1

ПРИМЕЧАНИЕ: Вам необходимо следить за решением IEnumerable<T>, так как это приведет к открытию файла на время обработки.

Например, с ответом Марк Gravell в:

foreach(var record in ReadFrom("myfile.csv")) { 
    DoLongProcessOn(record); 
} 

файл будет оставаться открытым для всего процесса обработки.

+1

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

+6

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

+0

Да, это, вероятно, лучшее решение, чтобы держать файл открытым, но вам просто нужно быть в стороне от импликации. –

0

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

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

Наконец, я заметил, что вы использовали нижнюю строчку. Я знаю, что в Java существует разница между заглавной и не капитализированной строкой, но я думал, что в строчной строке C# была просто ссылка на заглавную строку?

public void Load(AddonCollection<T> collection) 
{ 
    // read from file 
    var query = 
     from line in LineReader(_LstFilename) 
     where line.Length > 0 
     select CreateAddon(line); 

    // add results to collection 
    collection.AddRange(query); 
} 

protected T CreateAddon(String line) 
{ 
    // create addon 
    T addon = new T(); 
    addon.Load(line, _BaseDir); 

    return addon; 
} 

protected static IEnumerable<String> LineReader(String fileName) 
{ 
    String line; 
    using (var file = System.IO.File.OpenText(fileName)) 
    { 
     // read each line, ensuring not null (EOF) 
     while ((line = file.ReadLine()) != null) 
     { 
      // return trimmed line 
      yield return line.Trim(); 
     } 
    } 
} 
+2

Почему вы передаете коллекцию методу Load? По крайней мере, назовите это LoadInto, если вы собираетесь это сделать;) –

Смежные вопросы