W3docs

Как читать файл построчно в Java

Читайте файл Java построчно с помощью BufferedReader, Files.lines, Files.readAllLines и Scanner.

Чтение текстового файла по одной строке за раз — одна из самых распространённых задач работы с файлами в Java. JDK предоставляет несколько способов это сделать; правильный выбор зависит от размера файла и от того, хотите ли вы использовать обычный цикл или потоковый конвейер. В этой главе показаны четыре идиоматических подхода и ситуации, в которых стоит применять каждый из них.

BufferedReader: основной инструмент потокового чтения

BufferedReader.readLine() читает одну строку за вызов и возвращает null в конце файла, поэтому хорошо сочетается с циклом while. Оберните его в try-with-resources, чтобы базовый ридер закрылся автоматически:

Path file = Path.of("notes.txt");
try (BufferedReader br = Files.newBufferedReader(file, StandardCharsets.UTF_8)) {
  String line;
  while ((line = br.readLine()) != null) {
    System.out.println(line);
  }
}

Это потоковое чтение файла: в памяти одновременно хранится только одна строка, поэтому такой подход справляется с многогигабайтным журналом без проблем. Files.newBufferedReader по умолчанию использует UTF-8, но явная передача кодировки документирует намерение и позволяет избежать платформенно-зависимого декодирования. Обратите внимание, что readLine() убирает символ перевода строки (\n, \r или \r\n), поэтому в возвращаемой строке он никогда не встречается.

Конструкция try (...) выше — это try-with-resources: она гарантирует закрытие ридера даже в случае исключения внутри цикла. Смотрите раздел буферизованные потоки, чтобы понять, почему оборачивание обычного ридера в BufferedReader важно для производительности.

Files.lines: то же самое через Stream

Когда нужен функциональный конвейер — фильтрация, преобразование, подсчёт — Files.lines предоставляет ленивый Stream<String> по строкам файла:

try (Stream<String> lines = Files.lines(Path.of("notes.txt"), StandardCharsets.UTF_8)) {
  lines.filter(s -> !s.isBlank())
       .map(String::trim)
       .forEach(System.out::println);
}

Как и BufferedReader, он читает лениво и никогда не загружает файл целиком. Проблема в том, что поток держит открытый дескриптор файла, поэтому его необходимо закрыть — всегда используйте его внутри try-with-resources, никогда как самостоятельное выражение. Забывание об этом ведёт к утечке дескрипторов.

Files.readAllLines: небольшие файлы целиком

Если файл небольшой и вам нужны все строки в виде List<String> сразу, Files.readAllLines — наиболее прямолинейный вариант:

List<String> all = Files.readAllLines(Path.of("notes.txt"), StandardCharsets.UTF_8);
for (String line : all) {
  System.out.println(line);
}

Этот метод жадный: весь файл декодируется в память до того, как вы обратитесь к первой строке. Это удобно для конфигурационных файлов и тестовых данных, но плохо подходит для больших файлов — в таких случаях предпочтительнее потоковые подходы. Scanner с nextLine() и hasNextLine() — четвёртый вариант, удобный когда нужно также разбирать токены, но он медленнее и легко использовать неправильно, поэтому три вышеописанных подхода покрывают большинство случаев. Для более широкого обзора файлового ввода-вывода — открытие, чтение целых файлов и NIO API Files — смотрите раздел чтение файлов в Java.

ПодходПамятьВозвращаетЛучше всего для
BufferedReader.readLine()Одна строкаОбычный циклБольшие файлы, ручное управление
Files.lines()Одна строка (лениво)Stream<String>Конвейеры для больших файлов
Files.readAllLines()Весь файлList<String>Небольшие файлы, произвольный доступ
Scanner.nextLine()Одна строкаОбычный циклСмешанный разбор строк и токенов

Практический пример: все три подхода рядом

Эта программа создаёт небольшой временный файл (чтобы пример был самодостаточным), а затем читает его тремя способами — циклом BufferedReader, потоком Files.lines и жадным Files.readAllLines:

java— editable, runs on the server

Что можно вынести из запуска:

  • Цикл BufferedReader нумерует четыре строки, и строка 3: [] показывает, что пустая строка в файле возвращается как пустая строка, а не пропускается — readLine() сообщает о каждой строке, включая пустые.
  • readLine() вывел каждую строку без завершающего \n, подтверждая, что символ перевода строки убирается; единственные скобки вокруг текста — это буквальные [ и ], добавленные самим кодом.
  • Files.lines подсчитал non-blank lines: 3, потому что filter(s -> !s.isBlank()) отбросил пустую строку — потоковый конвейер лениво обрабатывает те же четыре строки, что видел цикл.
  • Files.readAllLines вернул total lines: 4 и first line : alpha, доказывая, что весь файл был жадно загружен в List<String>, по которому можно обращаться через get(0).
  • Каждый ридер находился внутри try-with-resources (или возвращал управляемый List), поэтому файловый дескриптор и временный файл были освобождены до вывода done — без утечки дескрипторов.

Практика

Практика
Почему поток, возвращаемый Files.lines(), необходимо использовать внутри блока try-with-resources?
Почему поток, возвращаемый Files.lines(), необходимо использовать внутри блока try-with-resources?
Was this page helpful?