Java NIO – Using ByteBuffer

1. Introduction

Java provides a class ByteBuffer which is an abstraction of a buffer storing bytes. While just a little bit more involved than using plain byte[] arrays, it is touted as more performant. in this article, let us examine some uses of ByteBuffer and friends.

A ByteBuffer operates on a FileChannel which is a byte channel which has a current position. The FileChannel provides methods for reading from and writing to ByteBuffers.

2. Creating a ByteBuffer

To begin with, we need a ByteBuffer to read and write data into. Create a ByteBuffer as follows:

ByteBuffer buf = ByteBuffer.allocate(2048);

You could also initialize a ByteBuffer from an array of bytes. The ByteBuffer will be backed by the array and provide facilities such as current position, etc.

byte[] byteArray = new byte[2048];
ByteBuffer buf = ByteBuffer.wrap(byteArray);

3. Reading and Writing into a ByteBuffer

Once the ByteBuffer is allocated, you can read bytes from a file as follows.

FileInputStream in = new FileInputStream(inputFile);
int len = in.getChannel().read(buf);

To write out the data from the ByteBuffer into a file, do the following:

FileOutputStream out = new FileOutputStream(outputFile);
out.getChannel().write(buf);

4. Copying Files Using a ByteBuffer

With the plumbing out of the way, here is the loop to copy data from one file to another.

while( in.getChannel().read(buf) != -1 ) {
    buf.flip();
    out.getChannel().write(buf);
    buf.clear();
}

Reading from the InputStream channel returns -1 on hitting EOF (end-of-file). At that point flip the buffer. This sets the position of the pointer within the buffer to 0 and the limit to the last byte read. Following which, bytes can be transferred from the pointer position to the OutputStream channel till the limit. At which point, the buffer can be cleared to reset the position and the limit.

5. Other Ways of Writing to ByteByffer

Of course, reading bytes from a file channel is not the only way to write into a ByteBuffer. We have the following methods for writing data of various types into the ByteBuffer. These methods write the binary representation of the data into the ByteBuffer.

  1. putChar
  2. putDouble
  3. putFloat
  4. putInt
  5. putLong
  6. putShort

A special note: these methods write the byte values of the appropriate types into the ByteBuffer. For instance, putChar() puts the two bytes of a char into the ByteBuffer without encoding it with a character set. These methods can be used when a strict binary representation of the data needs to be stored and/or transported (for example across a socket).

6. Convert Integer to Byte Array

ByteBuffer is useful for binary conversion of data to and from Java primitive data. For instance suppose you want to convert an integer to an array of bytes. The following shows an integer and the hex value of the bytes.

int value = 389745347;
System.out.printf("%1$d => 0x%1$X", value);

//prints:
389745347 => 0x173B0AC3

To convert the integer to a byte array, you can use the ByteBuffer as folows. Allocate a ByteBuffer, write the integer value into it using putInt() and get the byte array. As simple as that.

ByteBuffer buf = ByteBuffer.allocate(10);
buf.putInt(value);
buf.flip();
byte[] arr = buf.array();
System.out.print(" { ");
for (int i = 0 ; i < buf.limit() ; i++)
    System.out.printf("0x%X, ", arr[i]);
System.out.println("}");

// prints:
{ 0x17, 0x3B, 0xA, 0xC3, }

In a similar way, you can convert the other primitive types to a byte array and back.

Similarly, storing and restoring a BigInteger is a bit involved because the ByteBuffer does not provide methods to directly read or write a BigInteger from a ByteBuffer. In the next section, we show how to do that.

7. BigInteger to Byte Array

The BigInteger is an arbitrary precision integer provided in Java. Most math operations including addition, subtraction, etc are provided by the BigInteger class. One way of storing and transmitting a BigInteger is to convert it to a byte array and use a ByteBuffer. Let’s see how to do that.

Convert a BigInteger to a byte array, show the byte representation and store it in a file.

BigInteger bint = new BigInteger("23784328748372478");
byte[] arr = bint.toByteArray();
System.out.printf("%1$s => { ", bint.toString());
for (int i = 0 ; i < arr.length ; i++)
    System.out.printf("0x%X, ", arr[i]);
System.out.println("}");
FileOutputStream out = new FileOutputStream("bignum");
ByteBuffer buf = ByteBuffer.allocate(25);
buf.put(arr);
buf.flip();
out.getChannel().write(buf);
out.close();

// prints:
23784328748372478 => { 0x54, 0x7F, 0xB8, 0x92, 0x44, 0x91, 0xFE, }

// show contents of file: od -t x1 bignum
0000000 54 7f b8 92 44 91 fe
0000007

Now let us read the file, load the bytes into a ByteBuffer and convert to a BigInteger:

FileInputStream in = new FileInputStream("bignum");
ByteBuffer buf = ByteBuffer.allocate(25);
in.getChannel().read(buf);
buf.flip();
byte[] arr = Arrays.copyOf(buf.array(), buf.limit());
BigInteger bint = new BigInteger(arr);
System.out.printf("%1$s => { ", bint.toString());
for (int i = 0 ; i < arr.length ; i++)
    System.out.printf("0x%X, ", arr[i]);
System.out.println("}");

// prints:
23784328748372478 => { 0x54, 0x7F, 0xB8, 0x92, 0x44, 0x91, 0xFE, }

Summary

A ByteBuffer is a buffer which provides for transferring bytes from a source to a destination. In addition to storage like a buffer array, it also provides abstractions such as current position, limit, capacity, etc. A FileChannel is used for transferring data to and from a file to a ByteBuffer. ByteBuffer also provides methods to directly store and restore the bytes of primitive types such as integer, short, long, etc.