How to set the byte order of a NIO buffer
When reading data items larger than a byte from
a buffer, the byte order of the various bytes making up the piece of data becomes
important. The simplest example is a 2-byte value. If we call the two bytes b1 and b2,
then together they represent 256 x b2 + b1. But in a file or stream (or buffer!),
there are two possible ways that we can order the bytes, depending on whether we put
b1 or b2 first:
- in little-endian ordering, we put the byte representing the "little end"
(or numerically smallest portion) of
the number first: in this case, b1;
- in big-endian ordering, we put the byte representing the "big end" first:
in this case, b2.
When more than 2 bytes are involved, by far the most common convention is still to use either
big or little endian: in other words, either the least or most numerically significant
byte comes first. And so these are the two orderings supported by Java Buffer classes.
(Another ordering called middle endian or mixed endian
is possible but rare, and not currently supported by NIO.) To specify either big or little
endian, call the following:
ByteBuffer buffer = ...
buffer.order(ByteOrder.LITTLE_ENDIAN);
// ... read/write some little-endian numbers ...
buffer.order(ByteOrder.BIG_ENDIAN);
// ... read/write some big-endian numbers ...
Byte order is thus considered to be a poperty of the buffer, not of
an individual value from the buffer. (And it's arguably not so common to mix orderings within
a single data structure.) But you can change a buffer's ordering at any time.
Determining the current byte order of a buffer
Usually it isn't a mystery, but if you need to find the current order of a buffer,
you can call order() with no parameters. So here's how to find out if a buffer
is little-endian:
if (buffer.order() == ByteOrder.LITTLE_ENDIAN) {
System.out.println("I'm little-endian!");
}
Default byte order
A ByteBuffer's default byte order is big endian. This is the
order commonly used in networking protocols and by various non-Intel hardware such
(e.g. SPARC and Motorola chips are big-endian).
The byte order of a duplicate or sliced buffer
There is a gotcha to be aware of: the byte order of a buffer is not preserved
when you make a duplicate or "slice" of that buffer. That is, when you create one buffer object
from another, with the second in effect being a "view" of part of the original buffer. For example:
// Create a buffer and set it as little-endian
ByteBuffer origBuff = ByteBuffer.allocate(100);
origBuff.order(ByteOrder.LITTLE_ENDIAN);
// Now create a slice of the buffer
ByteBuffer slice = origBuff.slice();
// 'slice' now refers to the same data, but is BIG ENDIAN!
In the above code, slice will be a separate buffer object that is still "backed"
by the same actual data. But slice will be a big-endian view of that data, because big-endian
is the default. The
endianness of origBuff is not transferred over when the new view of the buffer is created.
Using your hardware's native ordering
Just occasionally it may useful to read or write data to a buffer in the current hardware's
"native" ordering (that is, the order in which the your machine's CPU writes words to memory).
This can be determined by:
which will return either ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN.
So you can call:
buffer.order(ByteOrder.nativeOrder());
You might use this, for example, if you have written a native library that you have compiled
for various architectures (which thus writes data to a file or memory in the machine's native
byte order), but where you want the Java part of your application to retain
the same code base across architectures. Or you might just want to make sure that
you are reading and writing data in the most efficient way for your system.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.