Creating Buffers
java.nio has seven basic buffer classes for the seven different primitive data types:
- ByteBuffer
- ShortBuffer
- IntBuffer
- CharBuffer
- FloatBuffer
- DoubleBuffer
- LongBuffer
All seven buffer classes have very similar APIs that differ primarily in the return types of their get methods and the argument types of their put methods. Of these seven classes, ByteBuffer is by far the most important. For instance, the read( ) and write( ) methods in FileChannel will only take a ByteBuffer as an argument. However, there are ways to create views of a ByteBuffer as one of the other types so you can still write ints or chars or doubles onto a channel that works only with bytes. The patterns are very similar to how a DataOutputStream enables you to write ints or chars or doubles onto a stream that expects to receive bytes.
You create new buffers primarily in two ways: by allocation and by wrapping. Allocation creates a new buffer backed by memory, whereas wrapping uses a buffer as an interface to an existing array.
Allocation is straightforward. Simply pass the desired capacity of the buffer to the static allocate( ) method in the class you want to instantiate. For example, this statement creates a new ByteBuffer with room for 1024 bytes:
ByteBuffer bBuffer = ByteBuffer.allocate(1024);
This statement creates a new IntBuffer with room for 500 ints:
IntBuffer iBuffer = IntBuffer.allocate(500);
Both of these will be backed by an array. That is, bBuffer contains a byte array of length 1024 and iBuffer contains an int array of length 500. You can retrieve references to these backing arrays using the array( ) methods:
int[] iData = iBuffer.array( ); byte[] bData = bBuffer.array( );
If you already have data in an array, you can build a buffer around it using the wrap( ) methods. For example:
int[] data = {1, 3, 2, 4, 23, 53, -9, 67}; IntBuffer buffer = IntBuffer.wrap(data);
In both cases, the arrays are not copies. These are the actual internal arrays where the buffer holds its data. Changing the data in these arrays changes the buffer's contents too, and vice versa.
However, there is another option, and this is where things get very interesting. Not all buffers are backed by Java arrays. You can allocate an array directly using the allocateDirect( ) methods instead of allocate( ):
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
Direct buffers can also be created by memory mapping a file. In this case, a lot of I/O operations may be performed directly on the files without copying them to RAM first.
The API to a direct buffer is exactly the same as to an indirect buffer, aside from the factory method that creates it. However, internally the computer may use different optimization techniques. For instance, it may map the buffer directly into main memory instead of going through an intermediate Java array object. Furthermore, it will make extra efforts to store the data in a contiguous memory block.
Such tricks can dramatically improve performance for large buffers. However, allocating a direct buffer takes longer than allocating an indirect buffer, so direct buffers are likely to be at best a wash and at worst significantly slower for smaller buffers than the indirect, array-backed buffers. Furthermore, the exact implementation details for the direct buffers are highly platform dependent. On some platforms, direct buffers offer a huge performance boost. On others, performance ranges from about the same as with streams to substantially worse. If performance is your primary concern, be sure to measure carefully both before and after using direct buffers.
Should you later need to find out whether a particular buffer has been allocated directly or indirectly, the isDirect( ) method will tell you:
public abstract boolean isDirect( )