Gymterview
middle

Что такое ByteBuffer и как с ним работать?

ByteBuffer — это буфер для хранения байтовых данных, являющийся ключевым компонентом пакета java.nio. Он используется для эффективного чтения и записи данных через каналы NIO.

Аналогия из жизни: ByteBuffer — это лоток для документов с указателем «текущая позиция». Вы кладёте документы (write), переворачиваете лоток (flip) и начинаете забирать документы (read). Метод clear — очистить лоток для новых документов.

Свойства буфера

Буфер имеет четыре ключевых свойства, всегда удовлетворяющих инварианту 0 <= mark <= position <= limit <= capacity:

Свойство Описание
capacity Максимальная ёмкость, задаётся при создании и не изменяется
position Индекс следующего элемента для чтения или записи
limit Граница, за которой чтение/запись невозможны
mark Сохранённая позиция для возврата через reset()

Создание ByteBuffer

Пример
ByteBuffer heapBuffer = ByteBuffer.allocate(1024);         // в куче JVM
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);  // в нативной памяти
ByteBuffer wrappedBuffer = ByteBuffer.wrap(new byte[1024]); // обёртка массива

Heap-буфер хранится в куче JVM, быстрее создаётся и управляется GC. Direct-буфер выделяется в нативной памяти ОС, медленнее создаётся, но эффективнее при интенсивном I/O (нет копирования между heap и нативной памятью). Direct-буферы рекомендуется использовать для долгоживущих объектов с частыми операциями ввода/вывода.

Основные операции

Пример
ByteBuffer buffer = ByteBuffer.allocate(1024);

// Запись
buffer.put((byte) 65);
buffer.putInt(42);
buffer.put("Hello".getBytes(StandardCharsets.UTF_8));

// Переключение в режим чтения (обязательно!)
buffer.flip();  // limit = position, position = 0

// Чтение
byte b = buffer.get();
int num = buffer.getInt();

// Подготовка к повторной записи
buffer.clear();   // position = 0, limit = capacity (данные не стираются)
buffer.compact();  // непрочитанные данные сдвигаются в начало

Паттерн чтения из канала

Пример чтения
try (FileChannel channel = FileChannel.open(Path.of("data.bin"),
        StandardOpenOption.READ)) {
    ByteBuffer buffer = ByteBuffer.allocate(4096);
    while (channel.read(buffer) != -1) {
        buffer.flip();
        while (buffer.hasRemaining()) {
            byte b = buffer.get();
        }
        buffer.clear();
    }
}

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

  • Забывают вызвать flip() после записи перед чтением — читают «мусор».
  • Путают clear() и compact(): clear() отбрасывает все данные, compact() сохраняет непрочитанные.
  • Создают direct-буферы для мелких короткоживущих операций.
  • Не используют цикл hasRemaining() при записи в канал — channel.write() может записать не все данные за один вызов.

На собеседовании: объясните цикл жизни буфера (allocate -> put -> flip -> get -> clear), разницу между heap и direct буферами, и почему flip() обязателен. Это наиболее частые вопросы по ByteBuffer.