Channels и Buffers

Базовый Java
≈ 2 мин чтения

Другая модель движения данных

Классические потоки обычно воспринимаются как последовательность данных, которую мы читаем или записываем. В NIO часто используется другая связка: channel соединяет программу с источником или приёмником, а buffer хранит блок данных, который передаётся между ними.

Например, FileChannel работает с файлом, а ByteBuffer хранит байты для очередной операции чтения или записи.

try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    int read = channel.read(buffer);
}

Вызов read(buffer) записывает полученные из канала байты внутрь буфера. Сам буфер при этом хранит не только массив данных, но и состояние, которое показывает, где сейчас находится следующая операция.

position, limit и capacity

  • capacity — общий размер буфера;
  • position — позиция следующего чтения или записи;
  • limit — граница, дальше которой текущая операция идти не должна.

После того как канал записал данные в буфер, position стоит после последнего записанного байта. Чтобы затем читать эти данные из буфера, обычно вызывают flip(): он устанавливает предел на текущую позицию и переводит позицию в начало.

ByteBuffer buffer = ByteBuffer.allocate(8);
buffer.put((byte) 10);
buffer.put((byte) 20);

buffer.flip();
System.out.println(buffer.get()); // 10

Канал умеет больше, чем однонаправленный поток

Некоторые каналы поддерживают чтение и запись через один объект. FileChannel также умеет работать с текущей позицией в файле, читать или писать по указанной позиции и передавать блоки данных между каналами. Это полезно, когда требуется больше контроля, чем дают простые последовательные потоки.

Само слово NIO не означает «всё неблокирующее». FileChannel и работа с ByteBuffer полезны и в обычном блокирующем файловом IO. Неблокирующие каналы — отдельная возможность NIO, а не свойство каждого канала.