JavaRush /Курсы /C# SELF /Чтение текстовых файлов: S...

Чтение текстовых файлов: StreamReader

C# SELF
36 уровень , 2 лекция
Открыта

1. Введение

Давайте представим, что у нас снова стоит задача прочитать содержимое текстового файла. Допустим, у нас есть файл с полезными цитатами программистов, которые мы хотим выводить в наше уже хорошо знакомое текстовое приложение. Да, мы уже видели высокоуровневые методы типа File.ReadAllText, но что если файл большой, и нам нужно читать его не целиком, а построчно? Или мы хотим читать файл контролируемо, например, медленно подгружать строки, чтобы не улететь по памяти?

Именно тут приходит на помощь StreamReader — класс, который отлично подходит для чтения текстовых файлов построчно, поблочно, символ за символом — словом, гибко и удобно.

Как работает StreamReader?

StreamReader — это класс из пространства имён System.IO, который реализует чтение текстовых данных из потока (обычно это файл, но может быть и сетевой поток, и память). Он умеет читать данные в нужной кодировке, разбирает их в символы и строки, а вам возвращает удобные типы данных: строки и символы.

Если изобразить это в виде схемы, получится что-то вроде:


[ Файл (bytes) ] --(FileStream)--> [ StreamReader (разбирает коды символов) ] ---> [ Ваш код (string, char) ]
  • File (или другой источник байт): Даёт нам байты.
  • FileStream: Читает эти байты, как "канал".
  • StreamReader: Превращает байты в символы и строки с учётом кодировки (по умолчанию — UTF-8).

Почему не всегда стоит пользоваться только File.ReadAllText?

На практике файлы бывают большими. Даже очень большими (например, логи или csv на миллион строк). Пытаться прочитать такие файлы целиком в память — всё равно что пытаться за раз съесть весь торт: вкусно, но опасно для здоровья.
StreamReader читает файл "по кусочкам". Он не грузит весь файл в память, а подгружает и отдаёт ваш запрос на следующую строку или символ только когда это нужно. Это экономно и очень удобно.

2. Чтение файла целиком построчно

Создадим файл quotes.txt с такими строками:


Код — это поэзия.
Дебаг — это детектив.
"Не работает" — лучший баг-репорт.

Теперь добавим код к уже начатому нами учебному приложению (пусть это будет простая консольная программа, которую мы постепенно развиваем). Поставим файл рядом с .exe — пусть программа его читает и выводит содержимое построчно.

Код с комментариями:


// Получаем путь к файлу в папке с программой
string fileName = "quotes.txt";
string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

// Проверяем, существует ли файл
if (!File.Exists(filePath))
{
    Console.WriteLine($"Файл не найден: {filePath}");
    return;
}

// Открываем файл для чтения, используя StreamReader
using StreamReader reader = new StreamReader(filePath);
string? line;
int lineNumber = 1;

// Читаем файл построчно до конца
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine($"{lineNumber,2}: {line}");
    lineNumber++;
}

Что здесь происходит?

  • Мы вычисляем путь до нашего файла с помощью Path.Combine и AppDomain.CurrentDomain.BaseDirectory — так код работает одинаково на Windows, Linux и даже в Docker-контейнере.
  • Сначала проверяем, что файл действительно существует.
  • Через using создаём StreamReader и по одной строке читаем файл (метод ReadLine()).
  • В конце блока using файл закрывается, даже если вы забыли это сделать вручную или поймали исключение.

Визуализация: схема работы


[ quotes.txt ] --(FileStream)--> [ StreamReader ] --(ReadLine)--> [ string line ] --(Console.WriteLine)--> Экран
    ^                                                       
    |
AppDomain.CurrentDomain.BaseDirectory + Path.Combine

3. Особенности и нюансы использования

Как происходит чтение строк

Метод ReadLine() возвращает строку до первого символа новой строки (\n или \r\n). Когда файл заканчивается, возвращается null. Поэтому цикл обычно выглядит именно так:


string? line;
while ((line = reader.ReadLine()) != null)
{
    // обработка строки
}

Типичные ошибки при работе со StreamReader

Иногда возникает соблазн не использовать using, а просто написать так:


StreamReader reader = new StreamReader(filePath);
// ...
reader.Close();

Такая конструкция — как забыть закрыть за собой дверь в мороз: может случиться всё, что угодно. Если в процессе чтения возникнет ошибка (например, неожиданно пропадёт доступ к диску), код для закрытия файла не выполнится, и файл останется висеть открытым в системе.
Поэтому использовать using — не рекомендация, а реальная необходимость. Ваш будущий коллега-системный администратор скажет спасибо, что ваша программа не оставляет после себя зоопарк из "зависших" файлов.

Короткая запись с использованием using declaration

С современными версиями C# (начиная с 8.0) можно писать короче:


using StreamReader reader = new StreamReader(filePath);
string? line;
while ((line = reader.ReadLine()) != null)
{
    Console.WriteLine(line);
}

Dispose вызовется в конце текущего блока (метода).

4. Полезные нюансы

Чтение файла с неизвестной кодировкой

По умолчанию StreamReader работает с UTF-8. Но иногда встречаются файлы с другой кодировкой (например, Windows-1251 для старых русскоязычных текстов). В этом случае программа может выводить "?" или нечитаемые символы.

Можно явно указать кодировку:


using System.Text;

// Открываем файл как Windows-1251
using StreamReader reader = new StreamReader(filePath, Encoding.GetEncoding("windows-1251"));

Документация по кодировкам и Encoding

Чтение файла как одного большого текста

Иногда нужно получить весь файл одним текстом, но не через File.ReadAllText, а через StreamReader.


using StreamReader reader = new StreamReader(filePath);
string allText = reader.ReadToEnd();
Console.WriteLine(allText);
  • Метод ReadToEnd() читает всё содержимое файла с текущей позиции до конца как одну строку.
  • Для больших файлов это может быть неэффективно и есть риск OutOfMemoryException. Для файлов до нескольких мегабайт — вполне рабочий вариант.

Как это используется в реальной жизни

  • Обработка логов. Если у вас есть серверный файл с логами, его удобно читать по строкам, фильтровать нужные события и не грузить в память всё сразу.
  • Импорт CSV. Если вам нужно парсить большие таблицы, удобно построчно доставать данные и обрабатывать их по мере поступления.
  • Проверка наличия ключевых слов в больших текстовых файлах. Вы можете искать нужный текст среди миллионов строк, не боясь упереться в лимит памяти.
  • Модульные тесты. Файлы с тестовыми данными часто читаются по строкам с помощью StreamReader.

Сравнение методов чтения

Способ Когда использовать Преимущества Недостатки
File.ReadAllText
Маленькие файлы Одна строчка кода, быстро Большой файл – много памяти
StreamReader.ReadLine()
Любые, особенно большие файлы Чтение по строкам, мало памяти Чуть больше кода, сложнее логику строить
StreamReader.ReadToEnd()
Маленькие/средние файлы Гибко управлять кодировкой Тяжело с очень большими файлами

5. Практика

Давайте усложним задание. Пусть наша программа выводит только первые N строк файла, количество которых вводит пользователь. Согласитесь, иногда не хочется видеть сразу сотни строк — достаточно пары цитат для мотивации. :)

Вот как это реализовать:


using System;
using System.IO;

class Program
{
    static void Main()
    {
        string fileName = "quotes.txt";
        string filePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);

        if (!File.Exists(filePath))
        {
            Console.WriteLine($"Файл не найден: {filePath}");
            return;
        }

        Console.Write("Сколько строк вывести? ");
        string? input = Console.ReadLine();
        if (!int.TryParse(input, out int linesToShow) || linesToShow < 1)
        {
            Console.WriteLine("Ошибка: Введите корректное число больше 0.");
            return;
        }

        using StreamReader reader = new StreamReader(filePath);

        int current = 0;
        string? line;
        while (current < linesToShow && (line = reader.ReadLine()) != null)
        {
            Console.WriteLine(line);
            current++;
        }

        if (current == 0)
            Console.WriteLine("Файл пуст или первую строку не получилось прочитать.");
        else if (current < linesToShow)
            Console.WriteLine($"В файле было только {current} строк(и).");
    }
}

6. Важные моменты и типичные ловушки

Когда вы работаете с StreamReader, главное — помнить о правильном управлении ресурсами (см. предыдущую лекцию про IDisposable). Если забыть об этом, можно получить ошибку "файл уже используется другим процессом" или "слишком много открытых файлов".

Ещё одна тонкость — внимательнее с кодировкой. Если вы не уверены, что файл в UTF-8, явно задавайте нужную кодировку. Для этого есть второй параметр у конструктора StreamReader.

Ещё одна практическая "подножка" может быть, если файл обновляется по ходу работы программы (например, лог-файл). Не всегда можно сразу увидеть новые строки, если файл используется другими процессами — тогда нужно пересчитывать/перечитывать файл заново или использовать специальные методы, но об этом мы поговорим позднее.

2
Задача
C# SELF, 36 уровень, 2 лекция
Недоступна
Чтение первых N строк файла
Чтение первых N строк файла
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ