Подтвердить что ты не робот

Чтение файла по строке в С#

Я пытаюсь прочитать некоторые текстовые файлы, где каждая строка должна быть обработана. На данный момент я просто использую 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;
        }
    }
}
4b9b3361

Ответ 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();
        }
    }
}