W3docs

Чтение файлов в Java

Чтение текстовых и двоичных файлов в Java с помощью FileReader, BufferedReader, Scanner, Files.readString и потоков.

Существует пять распространённых способов прочитать текстовый файл в Java, и правильный выбор почти полностью зависит от размера файла и того, что вы хотите сделать с содержимым. В этой главе рассматриваются все пять — от простейшего до наиболее гибкого:

  1. Files.readString(path) — весь файл в виде одной String.
  2. Files.readAllLines(path) — весь файл в виде List<String>.
  3. Files.readAllBytes(path) — весь файл в виде byte[].
  4. Files.lines(path) — файл в виде ленивого Stream<String>.
  5. BufferedReader / Scanner — классические декораторы с полным контролем.

Выбирайте наименьший инструмент, подходящий для задачи. Чтение 4 ГБ лога с помощью Files.readString приводит к OutOfMemoryError; чтение 12-строчного конфигурационного файла через BufferedReader с циклом while — это шесть строк кода там, где хватило бы одной.

Files.readString(path) — весь файл за один вызов

String text = Files.readString(Path.of("config.json"), StandardCharsets.UTF_8);

Добавлен в Java 11. Возвращает весь файл в виде String. По умолчанию использует UTF-8 начиная с Java 18 (тем не менее Charset по-прежнему рекомендуется задавать явно, даже с новым значением по умолчанию). Выбрасывает IOException, если файл не существует или не может быть прочитан; выбрасывает OutOfMemoryError, если файл больше кучи.

Используйте когда: файл достаточно мал — конфигурационные файлы, JSON-данные, MDX-главы, всё, что можно прочитать в одном окне редактора. Неформальное правило: меньше нескольких мегабайт.

Files.readAllLines(path) — список строк

List<String> lines = Files.readAllLines(Path.of("hosts.txt"), StandardCharsets.UTF_8);

Возвращает неизменяемый List<String> строк файла с удалёнными разделителями строк. Профиль памяти аналогичен readString плюс накладные расходы List — также хранит весь файл в памяти.

Используйте когда: вам нужна индексация по номеру строки, сортировка файла или передача строк в цикл for (String line : lines) без настройки потоков.

Files.readAllBytes(path) — необработанные байты

byte[] raw = Files.readAllBytes(Path.of("photo.png"));

Байтовый эквивалент. Без Charset, так как декодирование не происходит. Используйте для бинарных файлов (изображений, архивов, исполняемых файлов) или когда нужно вычислить хэш или передать байты в ByteArrayInputStream.

Files.lines(path) — ленивый поток

try (Stream<String> lines = Files.lines(Path.of("app.log"), StandardCharsets.UTF_8)) {
  long errors = lines.filter(l -> l.contains("ERROR")).count();
}

Это единственный встроенный инструмент чтения, масштабируемый для файлов произвольного размера. Stream<String> является ленивым — строки читаются по требованию, а не все сразу — и напрямую сочетается с лексикой потоковых конвейеров (filter, map, count, toList).

Два обязательных требования:

  • try-with-resources обязателен. Поток владеет открытым дескриптором файла; без try-with-resources файл остаётся открытым до сборки мусора, и вы исчерпаете файловые дескрипторы на нагруженном сервере.
  • Не переиспользуйте поток после терминальной операции. Потоки одноразовые.

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

BufferedReader.readLine() — классический подход

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

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

Files.newBufferedReader(path) — это современная фабрика; классический вариант — new BufferedReader(new FileReader("hosts.txt")) (который использует кодировку платформы на JDK старше 18 — задавайте UTF-8 с помощью трёхаргументной перегрузки). Контракт readLine():

  • Возвращает следующую строку без разделителя (\n, \r или \r\n).
  • Возвращает null в конце файла. Условие цикла (line = readLine()) != null — устоявшаяся идиома.

BufferedReader также является производителем Stream<String>: reader.lines() возвращает Stream<String>, основанный на ридере. Именно так реализован Files.lines под капотом.

Scanner — посимвольный разбор

Scanner читает текст токенами — словами, целыми числами, числами с плавающей точкой, строками, даже совпадениями регулярных выражений — и является правильным инструментом для чтения структурированного ввода, где единицы не являются целыми строками.

try (Scanner sc = new Scanner(Files.newBufferedReader(Path.of("nums.txt")))) {
  while (sc.hasNextInt()) {
    int n = sc.nextInt();
    System.out.println(n * n);
  }
}

Scanner медленнее BufferedReader, так как выполняет разбор; он выделяет короткие строки и запускает регулярные выражения. Для построчной обработки предпочтите BufferedReader. Для типизированных токенов из небольшого файла (числа, слова, CSV-подобный ввод) Scanner избавляет от слоя разбора.

Полная глава о Scanner находится далее в этой части — здесь рассмотрен вариант чтения файла.

FileReader — необработанный символьный ридер

try (FileReader in = new FileReader("notes.txt", StandardCharsets.UTF_8)) {
  int c;
  while ((c = in.read()) != -1) {
    System.out.print((char) c);
  }
}

FileReader читает символы непосредственно из файла — без буферизации, без учёта строк, без сделанных за вас выборов декодирования (вы передаёте Charset или принимаете умолчание платформы на JDK до версии 18). Это слой, поверх которого построены остальные инструменты. В прикладном коде его почти никогда не используют напрямую — его оборачивают в BufferedReader.

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

Что выбрать

СценарийВыбор
Небольшой файл нужен как единая StringFiles.readString
Небольшой файл нужен как List<String>Files.readAllLines
Бинарный файл (изображение, архив)Files.readAllBytes
Любой файл с потоковым преобразованиемFiles.lines (внутри try-with-resources)
Построчный цикл с полным контролемFiles.newBufferedReader + readLine
Типизированные токены (числа, слова, совпадения regex)Scanner
По одному символу, крошечный файлFileReader

Правильный вариант по умолчанию для случая «просто хочу загрузить этот небольшой текстовый файл» — Files.readString. Правильный вариант по умолчанию для «обработать этот огромный лог без переполнения памяти» — Files.lines.

Практический пример: один файл, пять ридеров

Программа ниже записывает небольшой текстовый файл, а затем читает его пятью разными способами — readString, readAllLines, Files.lines с фильтрацией через Predicate<String> из словаря части 12, BufferedReader.readLine и Scanner для токенизированных целых чисел. Каждый блок выводит то, что получил, чтобы вы могли видеть формы рядом.

java— editable, runs on the server

Что вынести из этого примера:

  • Files.readString вернул весь файл в виде одной String — просто и именно то, что нужно для небольших конфигов и шаблонов. Для 4 ГБ лога это привело бы к OutOfMemoryError.
  • Files.readAllLines вернул индексируемый List<String> со срезанными разделителями. lines.get(0) сработал, потому что список материализован в памяти; с потоком так сделать не получится.
  • Files.lines(file) был открыт внутри try-with-resources, так как поток владеет дескриптором файла. Конвейер .filter(isError).count() имеет ту же форму, что и в части 12 — изменился только источник.
  • BufferedReader.readLine() вернул null в конце файла. Цикл for здесь остановился на трёх намеренно, но в продакшн-коде идиома — while ((line = in.readLine()) != null).
  • Scanner пропустил строки, не начинающиеся с целого числа, затем читал токены с помощью nextInt(), пока они не закончились. Тот же Scanner мог бы читать числа с плавающей точкой (nextDouble), совпадения регулярных выражений (findInLine) или BigInteger — вот почему он стоит дороже на токен, чем BufferedReader на строку.

Что дальше

Следующая глава, Запись файлов в Java, охватывает сторону записи тех же API — Files.writeString, Files.write, BufferedWriter, PrintWriter и флаги StandardOpenOption (APPEND, CREATE_NEW, TRUNCATE_EXISTING), которые определяют, как обрабатывается существующий файл.

Практика

Практика
Вам нужно обработать 5 ГБ серверного лога построчно, считая строки, содержащие слово `ERROR`. Какой ридер подходит для этой задачи?
Вам нужно обработать 5 ГБ серверного лога построчно, считая строки, содержащие слово `ERROR`. Какой ридер подходит для этой задачи?
Was this page helpful?