Чтение файла по строке в С#
Я пытаюсь прочитать некоторые текстовые файлы, где каждая строка должна быть обработана. На данный момент я просто использую StreamReader, а затем каждый раз читаю каждую строку.
Мне интересно, есть ли более эффективный способ (с точки зрения 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;
}
}
}
Ответы
Ответ 1
С помощью блока итератора вы можете легко писать линейный читатель на основе 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" имеет некоторый причудливый код, позволяющий выполнять агрегацию данных, но отбрасывать его (без буферизации). Джон объясняет здесь.
Ответ 2
Проще всего читать строку и проверять, не является ли она нулевым, чем проверять 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>
).
Ответ 3
ПРИМЕЧАНИЕ. Вам нужно следить за решением IEnumerable<T>
, так как это приведет к открытию файла на время обработки.
Например, с ответом Марка Гравелла:
foreach(var record in ReadFrom("myfile.csv")) {
DoLongProcessOn(record);
}
файл останется открытым для всей обработки.
Ответ 4
Спасибо всем за ваши ответы! Я решил пойти со смесью, в основном сосредоточившись на Марке, хотя мне нужно будет только читать строки из файла. Я думаю, вы могли бы утверждать, что разделение необходимо везде, но хе, жизнь слишком коротка!
Что касается сохранения файла открытым, это не будет проблемой в этом случае, поскольку код является частью настольного приложения.
Наконец, я заметил, что вы использовали нижнюю строчку. Я знаю, что в Java существует разница между заглавной и не капитализированной строкой, но я думал, что в строчной строке С# была просто ссылка на заглавную строку?
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();
}
}
}