Gymterview
middle

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

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

Подходы для текстовых файлов

Files.lines() — возвращает ленивый Stream<String>, читающий строки по мере потребления:

Пример
try (Stream<String> lines = Files.lines(Path.of("huge.txt"), StandardCharsets.UTF_8)) {
    long errors = lines.filter(l -> l.contains("ERROR")).count();
}

BufferedReader — классический подход с полным контролем:

Пример
try (BufferedReader reader = Files.newBufferedReader(Path.of("huge.txt"))) {
    String line;
    while ((line = reader.readLine()) != null) {
        // обработка строки
    }
}

Подходы для бинарных файлов

FileChannel + ByteBuffer — эффективное чтение блоками:

Пример
try (FileChannel channel = FileChannel.open(Path.of("huge.bin"),
        StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
    while (channel.read(buffer) != -1) {
        buffer.flip();
        while (buffer.hasRemaining()) { buffer.get(); }
        buffer.clear();
    }
}

MappedByteBuffer — файл отображается в виртуальную память, ОС подгружает страницы по мере обращения:

Пример memory-mapped чтения
try (FileChannel channel = FileChannel.open(Path.of("huge.dat"),
        StandardOpenOption.READ)) {
    long fileSize = channel.size();
    // Для файлов > 2 ГБ — чтение по частям
    long position = 0;
    int chunkSize = 256 * 1024 * 1024; // 256 МБ
    while (position < fileSize) {
        long remaining = fileSize - position;
        int mapSize = (int) Math.min(chunkSize, remaining);
        MappedByteBuffer buffer = channel.map(
            FileChannel.MapMode.READ_ONLY, position, mapSize);
        while (buffer.hasRemaining()) { buffer.get(); }
        position += mapSize;
    }
}

Сравнение подходов

Подход Тип данных Память Скорость Удобство
Files.lines() Текст Низкая Средняя Высокое
BufferedReader Текст Низкая Средняя Среднее
FileChannel + ByteBuffer Бинарные Низкая (размер буфера) Высокая Низкое
MappedByteBuffer Бинарные Управляется ОС Очень высокая Среднее

Частые ошибки

  • Используют Files.readAllBytes() или Files.readAllLines() для больших файлов — OutOfMemoryError.
  • Забывают закрывать Stream<String> от Files.lines() — утечка файловых дескрипторов.
  • Применяют MappedByteBuffer для мелких файлов — накладные расходы перевешивают выгоду.
  • Для файлов больше 2 ГБ MappedByteBuffer нужно создавать по частям, так как map() принимает int size.

На собеседовании: назовите Files.lines() для текста и FileChannel + ByteBuffer для бинарных данных как основные подходы. Покажите понимание того, что ленивость Stream означает чтение по мере потребления, а не загрузку всего файла.