Bytes
Jakob Jenkov |
The Mem Ops Bytes class represents a sequence of bytes from a byte array. It can be a sequence
of bytes from a shared byte array, as obtained from one of the byte allocators
BytesAllocatorAutoDefrag or
BytesAllocatorManualDefrag, or a byte array created via the
Java new
operator.
To avoid fragmenting the Java heap with Bytes
instances, the Bytes
class is designed
to be obtained from an ObjectPool . Thus, the Bytes
object can be
returned to the ObjectPool
after the byte sequence it references has been freed back to the
bytes allocator they were allocated from.
Creating a Bytes ObjectPool
To use the Bytes
class effectively you should create a BytesAllocatorAutoDefrag
,
a BytesFactory
and an ObjectPool
. Here is how that looks:
BytesAllocatorAutoDefrag byteArrayAllocator = new BytesAllocatorAutoDefrag(new byte[1024 * 1024]); BytesFactory bytesFactory = new BytesFactory(byteArrayAllocator); ObjectPool<Bytes> objectPool = new ObjectPool<Bytes>(16,bytesFactory);
Obtaining a Bytes Instance
To obtain a Bytes
instance from the ObjectPool
you simply call the
ObjectPool
instance()
method. Here is an example of obtaining a Bytes
instance from the ObjectPool
:
Bytes bytes = objectPool.instance();
Allocate Byte Sequence
Once you have obtained a Bytes
instance from the ObjectPool
you will need to allocate
a sequence of bytes from the BytesAllocatorAutoDefrag
. Here is how you allocate a byte sequence
from the ByteAllocatorAutoDefrag
via the Bytes
instance:
bytes.allocate(1024);
This example allocates 1024 bytes from the ByteAllocatorAutoDefrag
.
Free Bytes
When you are done with the byte sequence, and the Bytes
instance, you need to free the byte sequence
and the Bytes
instance again. You do so by calling the Bytes
free()
method. Calling free()
will free the Bytes
instance back to the ObjectPool
and the allocated bytes back to the BytesAllocatorAutoDefrag
.
Here is an example of how to free a Bytes
instance including the byte sequence it has allocated:
bytes.free();
Bytes Fields
A Bytes
object has several fields which you can use to access and work with the allocated byte sequence
which the Bytes
object represents. Each of these fields will be covered in the following sections.
data
The data
Bytes
field is a reference to the byte array in which the byte sequence is allocated.
You need this reference in order to read bytes from, or write bytes to the allocated byte sequence. Here is an example:
byte value = bytes.data[someIndex]; bytes.data[someIndex] = 123;
startIndex
The startIndex
Bytes
field contains the index in the byte array referenced by the
data
field where your allocated byte sequence starts. The startIndex
field contains
the index of the first byte of this byte sequence.
length
The length
Bytes
field contains the length in bytes of the allocated byte
sequence represented by this Bytes
object.
endIndex
The endIndex
Bytes
field contains the first index after the last byte in the
allocated byte sequence. In other words, the byte referenced by endIndex
is not part of the byte
sequence, but the byte just before it is.
The endIndex
is equal to startIndex
+ length
.
Also, endIndex
will be equal to the
startIndex
of the next byte sequence allocated right after the byte sequence represented by this
Bytes
instance.
writeIndex
The writeIndex
Bytes
field is an index you can use when writing bytes into the allocated
byte sequence. The writeIndex
can be used to keep track of how many bytes you have already written to
the byte sequence. Also, in case you don't use all of the allocated bytes in the byte sequence, the
writeIndex
can help you keep track of how many bytes you actually wrote into the byte sequence.
For instance, imagine you have to write a response from a server back to a client. You know the response will never exceed 1 MB, so you allocate a byte sequence of 1 MB to hold the response. The response might only end up requiring e.g. 38 KB, so only these 38 KB of the 1 MB allocated byte sequence to should be written back to the client.
The writeIndex
field can then be used to keep track of the bytes written to the byte sequence.
In the example above, the writeIndex
will point to the first byte after the 38 KB byte written.
Thus, the bytes to write back to the client are the bytes from and including startIndex
to
but excluding writeIndex
. In other words, writeIndex
is like the endIndex
but of the actual bytes written, not of the whole allocated byte sequence.
readIndex
The readIndex
Bytes
field can be used when reading the bytes that were actually written
to the allocated byte sequence. For instance, if 38 KB were written into a 1 MB allocated sequence, then only the
first 38 KB of the allocated sequence should be read again. Thus, when sending back a response to a client,
you will have to read those 38 KB out of the byte sequence and write them to a network socket. The
readIndex
can be used to help you keep track of how many bytes you have actually read, in case you
are not able to read them all in one go.
Use Case Examples
In this section I will try to give you a few visual use case examples that shows how to use the Bytes
class, and its fields. The use cases are actually quite similar in how they use the Bytes
class, even
if they are doing different things.
Receiving and Processing a Message
Imagine your code runs in a server that receives messages from clients. When a message arrives you need to store it in memory before processing it, and then you need to process it too.
First, you start with a shared byte array from which you can allocate a byte sequence to store the incoming message:

Second, you allocate a byte sequence from this shared byte array which is big enough to handle any message that is within acceptable message size limits:

Third, you write the incoming message into the allocated byte sequence, advancing the writeIndex
while doing so:

Fourth, you start processing the message in the allocated byte sequence, advancing readIndex
as you
process more and more of the message:

Sending a Response to a Client
Imagine your code runs in a server that receives messages from clients. After processing a message you may need to send back a response to the client.
First, you start with a shared byte array from which you can allocate the byte sequence to generate the response into, before sending it back to the client:

Second, you allocate a byte sequence big enough to hold any response that is within acceptable response message size limits:

Third, you generate the response message into the allocated byte sequence, advancing the writeIndex
while generating the response:

Fourth, you need to read the generated response message and send it back to the client. While reading
and sending the generated response back, you advance the readIndex
.

Tweet | |
Jakob Jenkov |