Java Nio ByteBuffer Example
This article is a tutorial on demonstrating the usage of the Java Nio ByteBuffer. All examples are done in the form of unit tests to easily prove the expectations of the API.
1. Introduction
The ByteBuffer class is an abstract class which also happens to extend Buffer and implement Comparable. A Buffer is simply a linear finite sized container for data of a certain primitive type. It exhibits the following properties:
- capacity: the number of elements it contains
- limit : the index of where the data it contains ends
- position : the next element to be read or written
ByteBuffer has these properties but also displays a host of semantic properties of it’s own. According to the ByteBuffer API the abstraction defines six categories of operations. They are:
get(...)andput(...)operations that operate relatively (in terms of the current position) and absolutely (by supplying an index)- bulk
get(...)operation done relatively (in terms of the current position) which will get a number of bytes from the ByteBuffer and place it into the argumentarraysupplied to theget(...)operation - bulk
put(...)operation done absolutely by supplying anindexand the content to be inserted - absolute and relative
get(...)andput(...)operations that get and put data of a specific primitive type, making it convenient to work with a specific primitive type when interacting with the ByteBuffer - creating a “view buffer’ or view into the underlying ByteBuffer by proxying the underlying data with a Buffer of a specific primitive type
- compacting, duplicating and slicing a ByteBuffer
A ByteBuffer is implemented by the HeapByteBuffer and MappedByteBuffer abstractions. HeapByteBuffer further specializes into HeapByteBufferR (R being read-only), which will very conveniently throw a ReadOnlyBufferException and should you try to mutate it via it’s API. The MappedByteBuffer is an abstract class which is implemented by DirectByteBuffer. All of theHeapByteBuffer implementations are allocated on the heap (obviously) and thus managed by the JVM.
2. Technologies used
The example code in this article was built and run using:
- Java 1.8.101 (1.8.x will do fine)
- Maven 3.3.9 (3.3.x will do fine)
- Spring source tool suite 4.6.3 (Any Java IDE would work)
- Ubuntu 16.04 (Windows, Mac or Linux will do fine)
3. Overview
A ByteBuffer is created via the the two static factory methods:
allocate(int)this will allocate aHeapByteBufferwith the capacity specified by theintargumentallocateDirect(int)this will allocate aDirectByteBufferwith the capacity specified by theintargument
The ByteBuffer class affords us the luxury of a fluent interface through much of it’s API, meaning most operations will return a ByteBuffer result. This way we can obtain a ByteBuffer by also wrapping a byte [], slicing a piece of another ByteBuffer, duplicating an existing ByteBuffer and performing get(...)and put(...)operations against an existing ByteBuffer. I encourage you to review the ByteBuffer API to understand the semantics of it’s API.
So why the distinction between direct and non-direct? It comes down to allowing the Operating System to access memory addresses contiguously for IO operations (hence being able to shove and extract data directly from the memory address) as opposed to leveraging the indirection imposed by the abstractions in the JVM for potentially non-contiguous memory spaces. Because the JVM cannot guarantee contiguous memory locations for HeapByteBuffer allocations the Operating System cannot natively shove and extract data into these types of ByteBuffers. So generally the rule of thumb is should you be doing a lot of IO, then the best approach is to allocate directly and re-use the ByteBuffer. Be warned DirectByteBuffer instances are not subject to the GC.
4. Test cases
To ensure determinism we have been explicit about the Charset in use, therefore any encoding of bytes or decoding of bytes will use the explicit UTF-16BE Charset.
Relative Get and Put operations Test cases
public class RelativeGetPutTest extends AbstractTest {
@Test
public void get() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final byte a = buffer.get();
final byte b = buffer.get();
assertEquals("Buffer position invalid", 2, buffer.position());
assertEquals("'H' not the first 2 bytes read", "H", new String(new byte[] { a, b }, BIG_ENDIAN_CHARSET));
}
@Test
public void put() {
final ByteBuffer buffer = ByteBuffer.allocate(24);
buffer.put("H".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("e".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("l".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("l".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("o".getBytes(BIG_ENDIAN_CHARSET));
buffer.put(" ".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("e".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("a".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("r".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("t".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("h".getBytes(BIG_ENDIAN_CHARSET));
buffer.put("!".getBytes(BIG_ENDIAN_CHARSET));
assertEquals("Buffer position invalid", 24, buffer.position());
buffer.flip();
assertEquals("Text data invalid", "Hello earth!", byteBufferToString(buffer));
}
@Test
public void bulkGet() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final byte[] output = new byte[10];
buffer.get(output);
assertEquals("Invalid bulk get data", "Hello", new String(output, BIG_ENDIAN_CHARSET));
assertEquals("Buffer position invalid", 10, buffer.position());
}
@Test
public void bulkPut() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final byte[] output = new String("earth.").getBytes(BIG_ENDIAN_CHARSET);
buffer.position(12);
buffer.put(output);
assertEquals("Buffer position invalid", 24, buffer.position());
buffer.flip();
assertEquals("Text data invalid", "Hello earth.", byteBufferToString(buffer));
}
@Test
public void getChar() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
buffer.mark();
final byte a = buffer.get();
final byte b = buffer.get();
buffer.reset();
char value = buffer.getChar();
assertEquals("Buffer position invalid", 2, buffer.position());
assertEquals("'H' not the first 2 bytes read", "H", new String(new byte[] { a, b }, BIG_ENDIAN_CHARSET));
assertEquals("Value and byte array not equal", Character.toString(value), new String(new byte[] { a, b }, BIG_ENDIAN_CHARSET));
}
@Test
public void putChar() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
buffer.position(22);
buffer.putChar('.');
assertEquals("Buffer position invalid", 24, buffer.position());
buffer.flip();
assertEquals("Text data invalid", "Hello world.", byteBufferToString(buffer));
}
}
The above suite of test cases demonstrate relative get()and put()operations. These have a direct effect on certain ByteBuffer attributes (position and data). In addition to being able to invoke these operations with byte arguments or receive byte arguments we also demonstrate usage of the putChar()and getChar(...)methods which conveniently act on the matching primitive type in question. Please consult the API for more of these convenience methods
Absolute Get and Put operations Test cases
public class AbsoluteGetPutTest extends AbstractTest {
@Test
public void get() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final byte a = buffer.get(0);
final byte b = buffer.get(1);
assertEquals("Buffer position invalid", 0, buffer.position());
assertEquals("'H' not the first 2 bytes read", "H", new String(new byte[] { a, b }, BIG_ENDIAN_CHARSET));
}
@Test
public void put() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final byte[] period = ".".getBytes(BIG_ENDIAN_CHARSET);
int idx = 22;
for (byte elem : period) {
buffer.put(idx++, elem);
}
assertEquals("Position must remian 0", 0, buffer.position());
assertEquals("Text data invalid", "Hello world.", byteBufferToString(buffer));
}
@Test
public void getChar() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
char value = buffer.getChar(22);
assertEquals("Buffer position invalid", 0, buffer.position());
assertEquals("Invalid final character", "!", Character.toString(value));
}
@Test
public void putChar() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
buffer.putChar(22, '.');
assertEquals("Buffer position invalid", 0, buffer.position());
assertEquals("Text data invalid", "Hello world.", byteBufferToString(buffer));
}
}
The above suite of test cases demonstrate usage of the absolute variants of the get(...)and put(...)operations. Interestingly enough, only the underlying data is effected (put(...)) as the position cursor is not mutated owing to the method signatures providing client code the ability to provide an index for the relevant operation. Again convenience methods which deal with the various primitive types are also provided and we demonstrate use of the ...Char(...)variants thereof.
ViewBuffer Test cases
public class ViewBufferTest extends AbstractTest {
@Test
public void asCharacterBuffer() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final CharBuffer charBuffer = buffer.asCharBuffer();
assertEquals("Buffer position invalid", 0, buffer.position());
assertEquals("CharBuffer position invalid", 0, charBuffer.position());
assertEquals("Text data invalid", charBuffer.toString(), byteBufferToString(buffer));
}
@Test
public void asCharacterBufferSharedData() {
final ByteBuffer buffer = ByteBuffer.wrap("Hello world!".getBytes(BIG_ENDIAN_CHARSET));
final CharBuffer charBuffer = buffer.asCharBuffer();
assertEquals("Buffer position invalid", 0, buffer.position());
assertEquals("CharBuffer position invalid", 0, charBuffer.position());
final byte[] period = ".".getBytes(BIG_ENDIAN_CHARSET);
int idx = 22;
for (byte elem : period) {
buffer.put(idx++, elem);
}
assertEquals("Text data invalid", "Hello world.", byteBufferToString(buffer));
assertEquals("Text data invalid", charBuffer.toString(), byteBufferToString(buffer));
}
}
In addition to the various convenience get(...)and put(...)methods that deal with the various primitive types Download NOW!

