Как читать файл построчно в 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:
Что можно вынести из запуска:
- Цикл
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— без утечки дескрипторов.