Avatar ·

Как читать большой текстовый файл построчно в Java без утечки памяти?

Всем привет! Я новичок в Java, столкнулся с проблемой при чтении большого лог-файла (около 500 МБ). Мне нужно обработать каждую строку отдельно, но когда я пытаюсь прочитать весь файл целиком, программа вылетает с OutOfMemoryError. Вот как я пытался это сделать: ```java import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws Exception { File file = new File("bigfile.log"); Scanner scanner = new Scanner(file); while (scanner.hasNextLine()) { String line = scanner.nextLine(); // обработка строки System.out.println(line); } scanner.close(); } } ``` Но для больших файлов это работает очень медленно и жрет много памяти. Я слышал про BufferedReader, но не понимаю, как его правильно использовать. Подскажите, как эффективно читать файл построчно, чтобы не загружать всё в память сразу? Желательно с примером кода. Теги: java, производительность, файловый ввод-вывод, большие файлы <<>>

Правильный способ: BufferedReader + FileReader

Для чтения больших текстовых файлов построчно в Java используется связка BufferedReader и FileReader. BufferedReader буферизирует данные (по умолчанию 8 КБ), что значительно ускоряет чтение, а главное — он не загружает весь файл в оперативную память. Строки обрабатываются по одной, поэтому память расходуется только на текущую строку.

Базовый пример (Java 7+ с try-with-resources)

import java.io.*;

public class ReadLargeFile {
    public static void main(String[] args) {
        String filePath = "bigfile.log";
        
        try (BufferedReader reader = new BufferedReader(new FileReader(filePath))) {
            String line;
            while ((line = reader.readLine()) != null) {
                // Обрабатываем каждую строку
                System.out.println("Прочитана строка длиной: " + line.length());
            }
        } catch (IOException e) {
            System.err.println("Ошибка чтения файла: " + e.getMessage());
        }
    }
}

Что здесь важно:

  • try-with-resources — автоматически закрывает reader, даже если возникнет исключение (не нужно писать finally).
  • readLine() возвращает null при достижении конца файла — это условие выхода из цикла.
  • Память: в каждый момент времени в памяти находится только одна строка, а не весь файл.

Улучшенный вариант: указание кодировки

Если файл не в кодировке по умолчанию (например, UTF-8 или Windows-1251), используйте InputStreamReader:

try (BufferedReader reader = new BufferedReader(
        new InputStreamReader(new FileInputStream(filePath), "UTF-8"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // обработка
    }
}

Производительность: кастомный буфер

Для очень больших файлов (гигабайты) можно увеличить размер буфера:

int bufferSize = 64 * 1024; // 64 КБ
try (BufferedReader reader = new BufferedReader(
        new FileReader(filePath), bufferSize)) {
    // ... тот же цикл
}

Альтернатива: Files.lines() (Java 8+)

Современный и лаконичный способ через Stream API. Важно закрывать стрим, иначе ресурсы не освободятся:

import java.nio.file.*;
import java.util.stream.*;

try (Stream<String> lines = Files.lines(Paths.get("bigfile.log"))) {
    lines.forEach(line -> {
        // обработка строки
        System.out.println(line);
    });
} catch (IOException e) {
    e.printStackTrace();
}

Этот метод тоже читает файл лениво (lazy) — строки подгружаются по мере необходимости. Он автоматически использует UTF-8, если не указано иное.

Чего НЕ стоит делать:

  • Files.readAllLines() — загружает ВСЕ строки в список в памяти. Для больших файлов — гарантированный OutOfMemoryError.
  • Scanner с большими файлами — медленнее, чем BufferedReader, и тоже может вызвать проблемы с памятью при неправильном использовании.

Итог

Для построчного чтения больших файлов используйте BufferedReader (или Files.lines()). Всегда закрывайте ресурсы (try-with-resources). Не загружайте весь файл в память — обрабатывайте строки по одной.

<<>>

Альтернативный подход: Apache Commons IO

Если вы не хотите писать цикл вручную, можно использовать библиотеку Apache Commons IO. Она предоставляет готовые утилиты для работы с файлами.

Добавьте зависимость в Maven:

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.15.0</version>
</dependency>

Пример использования:

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import java.io.File;
import java.io.IOException;

public class ReadWithCommonsIO {
    public static void main(String[] args) {
        File file = new File("bigfile.log");
        
        try (LineIterator it = FileUtils.lineIterator(file, "UTF-8")) {
            while (it.hasNext()) {
                String line = it.nextLine();
                // обработка строки
                System.out.println("Строка: " + line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

LineIterator работает аналогично BufferedReader — читает строки по одной, не загружая весь файл. Плюс: код чище, меньше boilerplate. Минус: внешняя зависимость. Если вы уже используете Spring или другой фреймворк с Commons IO — это отличный выбор.

Также можно обработать файл через FileUtils.readLines(), но он возвращает список строк — не подходит для больших файлов.