Чтение файла строка за строкой в C#
Я пытаюсь прочитать некоторые текстовые файлы, где каждая строка должна быть обработана. На данный момент я просто использую StreamReader, а затем читаю каждую строку по отдельности.
мне интересно, есть ли более эффективный способ (с точки зрения LoC и читаемости) сделать это с помощью LINQ без ущерба для операционной эффективности. Примеры, которые я видел, включают загрузку всего файла в память, а затем его обработку. В этом случае, однако, я не думаю, что это было бы очень эффективный. В первом примере файлы могут достигать примерно 50k, а во втором примере не все строки файла должны быть прочитаны (размеры обычно
вы можете утверждать, что в настоящее время это не имеет большого значения для этих небольших файлов, однако я считаю, что такой подход приводит к неэффективному коду.
первый пример:
// 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;
}
}
}
4 ответа:
вы можете написать линейный считыватель на основе 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
etc.обратите внимание, что если вы используете
OrderBy
или стандартныйGroupBy
, он должен будет буферизировать данные в памяти; Если вам нужна группировка и агрегация, "PushLINQ" имеет какой-то причудливый код, позволяющий выполнять агрегации данных но отбросьте его (без буферизации). Объяснение Джона здесь.
проще прочитать строку и проверить, является ли она нулевой, чем постоянно проверять 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>
).
Примечание: вы должны следить за
IEnumerable<T>
решение, так как это приведет к тому, что файл будет открыт на время обработки.например, с ответом Марк Gravell-это:
foreach(var record in ReadFrom("myfile.csv")) { DoLongProcessOn(record); }
файл останется открытым для всей обработки.
спасибо всем за ваши ответы! Я решил пойти со смесью, в основном сосредоточившись на марке, хотя мне нужно будет только читать строки из файла. Я думаю, вы могли бы утверждать, что разделение необходимо везде, но хех, жизнь слишком коротка!
Что касается сохранения файла открытым, это не будет проблемой в этом случае, так как код является частью настольного приложения.
наконец я заметил, что вы все использовали строчную строку. Я знаю, что в 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(); } } }