|
View buffers
A “view buffer” allows you to look at an underlying ByteBuffer through the window of a particular primitive type. The ByteBuffer is still the actual storage that’s “backing” the view, so any changes you make to the view are reflected in modifications to the data in the ByteBuffer. As seen in the previous example, this allows you to conveniently insert primitive types into a ByteBuffer. A view also allows you to read primitive values from a ByteBuffer, either one at a time (as ByteBuffer allows) or in batches (into arrays). Here’s an example that manipulates ints in a ByteBuffer via an IntBuffer:
//: c12:IntBufferDemo.java
// Manipulating ints in a ByteBuffer with an IntBuffer
import java.nio.*;
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
public class IntBufferDemo {
private static Test monitor = new Test();
private static final int BSIZE = 1024;
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.allocate(BSIZE);
IntBuffer ib = bb.asIntBuffer();
// Store an array of int:
ib.put(new int[] { 11, 42, 47, 99, 143, 811, 1016 });
// Absolute location read and write:
System.out.println(ib.get(3));
ib.put(3, 1811);
ib.rewind();
while(ib.hasRemaining()) {
int i = ib.get();
if(i == 0) break; // Else we'll get the entire buffer
System.out.println(i);
}
monitor.expect(new String[] {
"99",
"11",
"42",
"47",
"1811",
"143",
"811",
"1016"
});
}
} ///:~
The overloaded put( ) method is first used to store an array of int. The following get( ) and put( ) method calls directly access an int location in the underlying ByteBuffer. Note that these absolute location accesses are available for primitive types by talking directly to a ByteBuffer, as well.
Once the underlying ByteBuffer is filled with ints or some other primitive type via a view buffer, then that ByteBuffer can be written directly to a channel. You can just as easily read from a channel and use a view buffer to convert everything to a particular type of primitive. Here’s an example that interprets the same sequence of bytes as short, int, float, long, and double by producing different view buffers on the same ByteBuffer:
//: c12:ViewBuffers.java
import java.nio.*;
import com.bruceeckel.simpletest.*;
public class ViewBuffers {
private static Test monitor = new Test();
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(
new byte[]{ 0, 0, 0, 0, 0, 0, 0, 'a' });
bb.rewind();
System.out.println("Byte Buffer");
while(bb.hasRemaining())
System.out.println(bb.position()+ " -> " + bb.get());
CharBuffer cb =
((ByteBuffer)bb.rewind()).asCharBuffer();
System.out.println("Char Buffer");
while(cb.hasRemaining())
System.out.println(cb.position()+ " -> " + cb.get());
FloatBuffer fb =
((ByteBuffer)bb.rewind()).asFloatBuffer();
System.out.println("Float Buffer");
while(fb.hasRemaining())
System.out.println(fb.position()+ " -> " + fb.get());
IntBuffer ib =
((ByteBuffer)bb.rewind()).asIntBuffer();
System.out.println("Int Buffer");
while(ib.hasRemaining())
System.out.println(ib.position()+ " -> " + ib.get());
LongBuffer lb =
((ByteBuffer)bb.rewind()).asLongBuffer();
System.out.println("Long Buffer");
while(lb.hasRemaining())
System.out.println(lb.position()+ " -> " + lb.get());
ShortBuffer sb =
((ByteBuffer)bb.rewind()).asShortBuffer();
System.out.println("Short Buffer");
while(sb.hasRemaining())
System.out.println(sb.position()+ " -> " + sb.get());
DoubleBuffer db =
((ByteBuffer)bb.rewind()).asDoubleBuffer();
System.out.println("Double Buffer");
while(db.hasRemaining())
System.out.println(db.position()+ " -> " + db.get());
monitor.expect(new String[] {
"Byte Buffer",
"0 -> 0",
"1 -> 0",
"2 -> 0",
"3 -> 0",
"4 -> 0",
"5 -> 0",
"6 -> 0",
"7 -> 97",
"Char Buffer",
"0 -> \0",
"1 -> \0",
"2 -> \0",
"3 -> a",
"Float Buffer",
"0 -> 0.0",
"1 -> 1.36E-43",
"Int Buffer",
"0 -> 0",
"1 -> 97",
"Long Buffer",
"0 -> 97",
"Short Buffer",
"0 -> 0",
"1 -> 0",
"2 -> 0",
"3 -> 97",
"Double Buffer",
"0 -> 4.8E-322"
});
}
} ///:~
The ByteBuffer is produced by “wrapping” an eight-byte array, which is then displayed via view buffers of all the different primitive types. You can see in the following diagram the way the data appears differently when read from the different types of buffers:
This corresponds to the output from the program.
Endians
Different machines may use different byte-ordering approaches to store data. “Big endian” places the most significant byte in the lowest memory address, and “little endian” places the most significant byte in the highest memory address. When storing a quantity that is greater than one byte, like int, float, etc., you may need to consider the byte ordering. A ByteBuffer stores data in big endian form, and data sent over a network always uses big endian order. You can change the endian-ness of a ByteBuffer using order( ) with an argument of ByteOrder.BIG_ENDIAN or ByteOrder.LITTLE_ENDIAN.
Consider a ByteBuffer containing the following two bytes:
If you read the data as a short (ByteBuffer.asShortBuffer( )), you will get the number 97 (00000000 01100001), but if you change to little endian, you will get the number 24832 (01100001 00000000).
Here’s an example that shows how byte ordering is changed in characters depending on the endian setting:
//: c12:Endians.java
// Endian differences and data storage.
import java.nio.*;
import com.bruceeckel.simpletest.*;
import com.bruceeckel.util.*;
public class Endians {
private static Test monitor = new Test();
public static void main(String[] args) {
ByteBuffer bb = ByteBuffer.wrap(new byte[12]);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays2.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.BIG_ENDIAN);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays2.toString(bb.array()));
bb.rewind();
bb.order(ByteOrder.LITTLE_ENDIAN);
bb.asCharBuffer().put("abcdef");
System.out.println(Arrays2.toString(bb.array()));
monitor.expect(new String[]{
"[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]",
"[0, 97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102]",
"[97, 0, 98, 0, 99, 0, 100, 0, 101, 0, 102, 0]"
});
}
} ///:~
The ByteBuffer is given enough space to hold all the bytes in charArray as an external buffer so that that array( ) method can be called to display the underlying bytes. The array( ) method is “optional,” and you can only call it on a buffer that is backed by an array; otherwise, you’ll get an UnsupportedOperationException.
charArray is inserted into the ByteBuffer via a CharBuffer view. When the underlying bytes are displayed, you can see that the default ordering is the same as the subsequent big endian order, whereas the little endian order swaps the bytes.
|
|