Получить доступ

NIO: каналы, буферы и неблокирующий ввод-вывод

io против nio, Channel и ByteBuffer с его flip/clear, Selector и event loop, влияние виртуальных потоков и NIO.2.

io против nio

Классический java.io поток-ориентирован и блокирующий: читаете из stream байт за байтом, и операция блокирует поток до готовности данных — модель «один поток на соединение».

java.nio буфер-ориентирован и поддерживает неблокирующий режим: данные ходят через каналы в буферы, а поток может не ждать, а спрашивать «есть ли готовые каналы». Это позволяет одному потоку обслуживать множество соединений.

Channel и ByteBuffer

Данные в NIO ходят через Channel (FileChannel, SocketChannel) в/из ByteBuffer. У буфера три ключевых указателя: capacity (размер), position (текущая позиция) и limit (граница чтения/записи).

Главная механика — flip(): после записи в буфер (например, channel.read(buf)) перед чтением из него вызывают flip(), который ставит limit на текущую position, а position на 0. Затем clear() или compact() готовят буфер к новой записи. Забыть flip() — классическая ошибка новичка в NIO (читается мусор или ноль байт).

ByteBuffer buf = ByteBuffer.allocate(1024);
try (FileChannel ch = FileChannel.open(path, StandardOpenOption.READ)) {
    ch.read(buf);  // пишем из канала в буфер
    buf.flip();    // переключаем буфер на чтение
    while (buf.hasRemaining()) process(buf.get());
}

Selector и event loop

Selector — сердце масштабируемого сетевого NIO: каналы регистрируют в нём интересующие события (готов к чтению/записи/принятию соединения), и один поток в цикле select() обрабатывает только готовые каналы. Это event loop — один (или несколько) потоков обслуживают тысячи соединений без потока на каждое.

На этом построены высоконагруженные серверы и фреймворки вроде Netty. Это объясняет, почему NIO «масштабируется лучше» для большого числа в основном простаивающих соединений, чем модель thread-per-connection.

Что изменили виртуальные потоки

Раньше выбор стоял остро: простой блокирующий код (io, поток на соединение) НЕ масштабировался, поэтому ради нагрузки писали сложный неблокирующий NIO/реактивный код. С виртуальными потоками (Java 21) блокирующий thread-per-request снова масштабируется до сотен тысяч соединений (см. отдельный урок).

Поэтому сегодня ручной non-blocking NIO с Selector нужен реже — он остался уделом высокопроизводительных фреймворков, а прикладной код часто может вернуться к простому блокирующему стилю на виртуальных потоках.

NIO.2 и off-heap

NIO.2 (java.nio.file) — это современный файловый API: Path/Files/FileSystem, обходы дерева Files.walk, ленивое Files.lines, слежение за изменениями WatchService. Им и пользуются вместо устаревшего java.io.File.

Для производительности есть direct buffers (ByteBuffer.allocateDirect — память вне кучи, меньше копирований при IO) и memory-mapped files (MappedByteBuffer — отображение файла в память для быстрого произвольного доступа). Это инструменты горячих путей, а не дефолт.

Как отвечать на собеседовании

Противопоставьте io (поток-ориентированный, блокирующий, поток-на-соединение) и nio (буфер-ориентированный, неблокирующий). Объясните связку Channel/ByteBuffer и обязательно механику flip(), и роль Selector как event loop (один поток — много соединений, основа Netty). Зрело добавьте, что виртуальные потоки вернули жизнеспособность блокирующему стилю, и упомяните NIO.2/direct buffers/mmap.

Вопросы по теме

Читайте также

Закрытая бета

Знать тему — половина дела

На живом собеседовании sotto слышит вопрос интервьюера и показывает короткий ответ и план. Оставьте контакт — пришлём доступ и промокод.

  • Промокод на 2 часа — сверх пробных 30 минут
  • Программа для Windows — скачать сразу, войти — через Яндекс ID или VK ID
  • Прямая связь с разработчиком: что улучшить — сделаем