Перейти к основному содержанию

Bytebuf

Обзор

ActiveJ нацелен на эффективный, но высокоуровневый ввод-вывод. Это требует широкого использования пользовательского пространства байтовых буферов. К сожалению, традиционные Java ByteBuffers создают большую нагрузку на GC.

Чтобы уменьшить накладные расходы на GC, ActiveJ вводит свои собственные удобные для GC и легкие ByteBufs, которые могут быть повторно использованы с ByteBufPool.

Кроме того, распространенная схема ввода-вывода заключается в том, чтобы рассматривать ByteBuffers как очередь: Операция ввода-вывода производит данные, а приложение их потребляет, или наоборот. ByteBufразработаны для облегчения этой модели, предоставляя специализированные ByteBufs класса с операциями, похожими на очередь, для нескольких ByteBufs.

ByteBuf

Чрезвычайно легкая и эффективная реализация по сравнению с Java NIO ByteBuffer. Нет прямых буферов, что упрощает и улучшает. ByteBuf производительность.

ByteBuf похож на очередь байтов FIFO и имеет две позиции: head и tail. Когда вы записываете данные в ByteBuf, его хвост увеличивается на количество записанных байт. Аналогично, при чтении данных из ByteBuf, это голова увеличивается на количество прочитанных байт.

Вы можете читать байты из ByteBuf только тогда, когда его хвост больше, чем голова. Аналогично, вы можете записывать байты в ByteBuf до тех пор, пока хвост ** не превысит длину обернутого массива. Таким образом, отпадает необходимость в операциях ByteBuffer.flip() .

ByteBuf поддерживает параллельные процессы: пока один процесс записывает некоторые данные в ByteBuf, другой процесс может их прочитать.

Для создания ByteBuf вы можете либо обернуть ваш байтовый массив в ByteBuf , либо выделить его из ByteBufPool.

note

Если вы создадите ByteBuf* без выделения его из ByteBufPool, вызов ByteBuf.recycle() не будет иметь никакого эффекта, такие ByteBufпросто собираются GC.

ByteBufPool

ByteBufPool позволяет повторно использовать ByteBufs и, как результат, снижает нагрузку на GC. сделать ByteBufPool Для более удобного использования существуют средства отладки и мониторинга выделенных ByteBufs, включая их стековые трассировки.

Чтобы получить ByteBuf из пула, используйте ByteBufPool.allocate(int size). Будет выделен ByteBuf размера, округленного до ближайшей ближайшей степени 2 (например, если размер равен 29, будет выделен ByteBuf размером 32 байта).

Чтобы вернуть ByteBuf в ByteBufPool, используйте ByteBuf.recycle(). В отличие от таких языков, как C/C, не требуется перерабатывать ByteBufs - в худшем случае они будут собраны GC.

Чтобы сделать все последовательным, ActiveJ опирается на концепцию "владения" (как в языке Rust) - после распределения компоненты передают byteBuf от одного к другому, пока последний "владелец" не вернет его в ByteBufPool.

Вы можете изучить пример использования ByteBufPool здесь.

ByteBufs

ByteBufs класс обеспечивает эффективное управление несколькими ByteBufs. Он создает оптимизированную очередь из нескольких ByteBufs с правилами FIFO.

Вы можете изучить пример использования ByteBufs здесь.

Коммунальные классы

Модуль ByteBuf также содержит утилитарные классы для управления и изменения размера базового байтового буфера, преобразования String и т.д.

Примеры

  • ByteBuf Пример - представляет некоторые основные ByteBuf возможности, такие как:

    • обертывание данных в ByteBuf для записи/чтения,
    • нарезка отдельных частей из данных,
    • конверсии.
  • ByteBuf Pool Пример - представляет, как работать с ByteBufPool.

  • ByteBufs Пример - показывает, как создаются и обрабатываются очереди ByteBufs.

note

Чтобы запустить примеры, необходимо клонировать ActiveJ с GitHub

git clone https://github.com/activej/activej

И импортируйте его как проект Maven. Посмотрите тег v5.4.3. Перед запуском примеров выполните сборку проекта. Эти примеры расположены по адресу activej/examples/core/bytebuf.

Пример ByteBuf

Если вы запустите пример, то получите следующий результат:

012345
[0, 1, 2, 3, 4, 5]
Здравствуйте
Нарезанный массив `ByteBuf`: [1, 2, 3]
Массив `ByteBuf`, преобразованный из `ByteBuffer`: [1, 2, 3]
  • Первые шесть строк являются результатом обертывания массива байтов в обертку ByteBuf для чтения и последующей печати:
byte[] data = new byte[]{0, 1, 2, 3, 4, 5};ByteBuf byteBuf = ByteBuf.wrapForReading(data);
  • Строка [0, 1, 2, 3, 4, 5] является результатом преобразования пустого массива байтов в ByteBuf и обертывания их для записи . Затем ByteBuf был заполнен байтами с помощью цикла while :
byte[] data = new byte[6];ByteBuf byteBuf = ByteBuf.wrapForWriting(data);byte value = 0;while (byteBuf.canWrite()) {  byteBuf.writeByte(value++);}
  • Строка "Hello" сначала была преобразована из String в ByteBuf и обернута для чтения, затем представлена как String для вывода с помощью byteBuf.asString():
String message = "Hello";ByteBuf byteBuf = ByteBuf.wrapForReading(message.getBytes(UTF_8));String unWrappedMessage = byteBuf.asString(UTF_8);
  • Последние два вывода представляют некоторые другие возможности ByteBuf, такие как нарезка:
byte[] data = new byte[]{0, 1, 2, 3, 4, 5};ByteBuf byteBuf = ByteBuf.wrap(data, 0, data.length);ByteBuf slice = byteBuf.slice(1, 3);

и преобразования стандартного ByteBuffer в ByteBuf:

ByteBuf byteBuf = ByteBuf.wrap(new byte[20], 0, 0);ByteBuffer buffer = byteBuf.toWriteByteBuffer();buffer.put((byte) 1);buffer.put((byte) 2);buffer.put((byte) 3);byteBuf.ofWriteByteBuffer(buffer);

Полный текст примера смотрите на GitHub.

Пример пула ByteBuf

Если вы запустите пример, то получите следующий результат:

Длина массива выделенных ByteBuf: 128Количество ByteBufs в пуле до утилизации: 0Количество ByteBuf'ов в пуле после переработки: 1Количество байтбафов в пуле: 0
Размер ByteBuf: 4Оставшиеся байты ByteBuf после записи 3 байт: 1Оставшиеся байты нового ByteBuf: 5
[0, 1, 2, 3, 4, 5]

Давайте посмотрим на реализацию:

public final class ByteBufPoolExample {  /* Setting ByteBufPool minSize and maxSize properties here for illustrative purposes.   Otherwise, ByteBufs with size less than 32 would not be placed into pool   */  static {    System.setProperty("ByteBufPool.minSize", "1");  }
  private static void allocatingBufs() {    // Allocating a ByteBuf of 100 bytes    ByteBuf byteBuf = ByteBufPool.allocate(100);
    // Allocated ByteBuf has an array with size equal to next power of 2, hence 128    System.out.println("Length of array of allocated ByteBuf: " + byteBuf.writeRemaining());
    // Pool has 0 ByteBufs right now    System.out.println("Number of ByteBufs in pool before recycling: " + ByteBufPool.getStats().getPoolItems());
    // Recycling ByteBuf to put it back to pool    byteBuf.recycle();
    // Now pool consists of 1 ByteBuf that is the one we just recycled    System.out.println("Number of ByteBufs in pool after recycling: " + ByteBufPool.getStats().getPoolItems());
    // Trying to allocate another ByteBuf    ByteBuf anotherByteBuf = ByteBufPool.allocate(123);
    // Pool is now empty as the only ByteBuf in pool has just been taken from the pool    System.out.println("Number of ByteBufs in pool: " + ByteBufPool.getStats().getPoolItems());    System.out.println();  }
  private static void ensuringWriteRemaining() {    ByteBuf byteBuf = ByteBufPool.allocate(3);
    // Size is equal to power of 2 that is larger than 3, hence 4    System.out.println("Size of ByteBuf: " + byteBuf.writeRemaining());
    byteBuf.write(new byte[]{0, 1, 2});
    // After writing 3 bytes into ByteBuf we have only 1 spare byte in ByteBuf    System.out.println("Remaining bytes of ByteBuf after 3 bytes have been written: " + byteBuf.writeRemaining());
    // We need to write 3 more bytes, so we have to ensure that there are 3 spare bytes in ByteBuf    // and if there are not - create new ByteBuf with enough room for 3 bytes (old ByteBuf will get recycled)    ByteBuf newByteBuf = ByteBufPool.ensureWriteRemaining(byteBuf, 3);    System.out.println("Amount of ByteBufs in pool:" + ByteBufPool.getStats().getPoolItems());
    // As we need to write 3 more bytes, we need a ByteBuf that can hold 6 bytes.    // The next power of 2 is 8, so considering 3 bytes that have already been written, new ByteBuf    // can store (8-3=5) more bytes    System.out.println("Remaining bytes of a new ByteBuf: " + newByteBuf.writeRemaining());
    // Recycling a new ByteBuf (remember, the old one has already been recycled)    newByteBuf.recycle();    System.out.println();  }
  private static void appendingBufs() {    ByteBuf bufOne = ByteBuf.wrapForReading(new byte[]{0, 1, 2});    ByteBuf bufTwo = ByteBuf.wrapForReading(new byte[]{3, 4, 5});
    ByteBuf appendedBuf = ByteBufPool.append(bufOne, bufTwo);
    // Appended ByteBuf consists of two ByteBufs, you don't have to worry about allocating ByteBuf    // with enough capacity or how to properly copy bytes, ByteBufPool will handle it for you    System.out.println(Arrays.toString(appendedBuf.asArray()));    System.out.println();  }
  public static void main(String[] args) {    allocatingBufs();    ensuringWriteRemaining();    appendingBufs();  }}

Полный текст примера смотрите на GitHub.

Пример ByteBufs

Если вы запустите пример, то получите следующий результат:

bufs:2 bytes:7
Buf взят из bufs: [0, 1, 2, 3]
Buf взят из bufs: [3, 4, 5, 6, 7, 8]
[1, 2, 3, 4][5, 6, 7, 8]Пуст ли 'ByteBufs'? правда

Первая строка представляет нашу очередь после добавления двух бафов: [0, 1, 2, 3] и [3, 4, 5] с помощью метода BUFS.add() .

BUFS.add(ByteBuf.wrapForReading(new byte[]{0, 1, 2, 3}));BUFS.add(ByteBuf.wrapForReading(new byte[]{3, 4, 5}));
// bufs consist of 2 Bufs at this moment

Затем применяется метод BUFS.take() и первый добавленный buf, а именно [0, 1, 2, 3], берется из очереди. Следующая строка представляет собой результат двух операций: добавления нового [6, 7, 8] buf и последующего применения BUFS.takeRemaining() , которая забирает все оставшиеся buf из очереди.

// Adding one more ByteBuf to bufsBUFS.add(ByteBuf.wrapForReading(new byte[]{6, 7, 8}));
ByteBuf takenBuf = BUFS.takeRemaining();
// Taken ByteBuf is combined of every ByteBuf that were in bufs
note

Обратите внимание на разницу между методами take() и poll() ByteBufs . При использовании take(), вы должны быть уверены, что в очереди остался хотя бы один ByteBuf , иначе используйте poll() , который может вернуть null.

Наконец, последние три строки представляют следующие операции:

  • Создание двух буфов: [1, 2, 3, 4] и [5, 6, 7, 8].
  • Слив очереди к потребителю, который печатает бафы.
  • Затем мы проверяем, пуста ли сейчас очередь.
import io.activej.bytebuf.ByteBuf;import io.activej.bytebuf.ByteBufs;
import java.util.Arrays;
public final class ByteBufsExample {  private static final ByteBufs BUFS = new ByteBufs();
  private static void addingToBufs() {    //[START REGION_1]    BUFS.add(ByteBuf.wrapForReading(new byte[]{0, 1, 2, 3}));    BUFS.add(ByteBuf.wrapForReading(new byte[]{3, 4, 5}));
    // bufs consist of 2 Bufs at this moment    //[END REGION_1]    System.out.println(BUFS);    System.out.println();  }
  private static void takingBufOutOfBufs() {    ByteBuf takenBuf = BUFS.take();
    // Buf that is taken is the one that was put in bufs first    System.out.println("Buf taken from bufs: " + Arrays.toString(takenBuf.asArray()));    System.out.println();  }
  private static void takingEverythingOutOfBufs() {    //[START REGION_2]    // Adding one more ByteBuf to bufs    BUFS.add(ByteBuf.wrapForReading(new byte[]{6, 7, 8}));
    ByteBuf takenBuf = BUFS.takeRemaining();
    // Taken ByteBuf is combined of every ByteBuf that were in bufs    //[END REGION_2]    System.out.println("Buf taken from bufs: " + Arrays.toString(takenBuf.asArray()));    System.out.println("Is 'ByteBufs' empty? " + BUFS.isEmpty());    System.out.println();  }
  public static void main(String[] args) {    addingToBufs();    takingBufOutOfBufs();    takingEverythingOutOfBufs();  }}

Полный текст примера смотрите на GitHub.