Что такое 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.