The ByteBuffer Class in Java
A backing array
As I mentioned earlier, creation of a non-direct buffer using
the allocate factory method also creates a backing array for the
buffer. This is confirmed by the code in Listing 13.
if(buf2.hasArray()) showArrayData(buf2.array()); Listing 13 |
Listing 13 invokes the hasArray method of the ByteBuffer
class to confirm the existence of a backing array, and then invokes the
array
method of that same class to get and display the contents of the array.
The output produced by this code is shown in Figure 13.
Show array data 0 0 0 1 2 20 21 22 0 0 Figure 13 |
As you should expect by now, the contents of the backing array shown in Figure 13 exactly match the contents of the buffer referred to by buf2 shown in Figure 12. From this point forward, changes to the buffer will change the contents of the backing array, and changes to the backing array will change the contents of the buffer.
Compacting a buffer
The code in Listing 14 invokes the compact method on the buffer,
after setting its position to 3. Then the code displays the contents
of the buffer twice, first without setting the position to zero,
and then again after setting the position to zero.
System.out.println("Compacting");
buf2.position(3);
buf2.compact();
showBufferData(buf2, "buf2");
buf2.position(0);
showBufferData(buf2, "buf2");
Listing 14
|
The compact method
Basically, the invocation of the compact method on a buffer discards the bytes between the beginning and the position, and slides the remaining bytes forward to the beginning.
Note that the bytes vacated at the upper end continue to contain the values that were there before the compact method was invoked (as opposed to filling in vacated elements with the value zero, for example).
Here is a partial description of the behavior as provided by Sun:
"The bytes between the buffer's current position and its limit, if any, are copied to the beginning of the buffer. ... The buffer's position is then set to n and its limit is set to its capacity."Sun goes on to explain:
"The buffer's position is set to the number of bytes copied, rather than to zero, so that an invocation of this method can be followed immediately by an invocation of another relative put method."The output
The output produced by the code in Listing 14 is shown in Figure 14.
Compacting Buffer data for buf2 22 0 0 Buffer data for buf2 1 2 20 21 22 0 0 22 0 0 Figure 14 |
The row of data containing only three values is the result of invoking showBufferData without first setting the value of the position property to zero. In this case, these are the elements that were vacated when the remaining data in the buffer was copied to the beginning of the buffer. In other words, these are the last three elements in the buffer. Note that they still contain the values that were there before the compact method was invoked.
The row of data containing ten values is the result of invoking showBufferData after setting the value of the position property to zero. The boldface values in this row correspond to the boldface values in Figure 12. As you can see, those values were slid to the left, leaving the original values in the three vacated bytes on the right.
Duplicating a buffer
The code in Listing 15 creates a new buffer that is a duplicate of the
buffer referred to by buf2. Then it displays the contents
of the original buffer and the new buffer.
System.out.println("Duplicating");
ByteBuffer buf3 = buf2.duplicate();
buf2.position(0);
showBufferData(buf2, "buf2");
buf3.position(0);
showBufferData(buf3, "buf3");
Listing 15
|
The duplicate method
Invoking the duplicate method on a buffer creates a new buffer that shares the original buffer's content. In the same sense that we have seen backing arrays linked to buffers, this process causes two buffers to be similarly linked. Changes to the original buffer's content will be reflected in the new buffer, and vice versa. However, position, limit, and mark values for the two buffers are independent of one another.
The new buffer's initial capacity, limit, position, and mark values are identical to those of the original buffer. The new buffer will be direct if the original buffer is direct, and it will be read-only if the original buffer is read-only.
The output
Figure 15 shows the output produced by the code in Listing 15.
Duplicating Buffer data for buf2 1 2 20 21 22 0 0 22 0 0 Buffer data for buf3 1 2 20 21 22 0 0 22 0 0 Figure 15 |
As you would expect from the name duplicate, the values in the two buffers are identical.
Demonstrate that the buffers are linked
The code in Listing 16 demonstrates that the two buffers are linked
as described above. This code changes the value at position
7 in one of the buffers, from 22 to 99, and then displays the contents
of both buffers.
buf3.put(7,(byte)99); buf2.position(0); showBufferData(buf2, "buf2"); buf3.position(0); showBufferData(buf3, "buf3"); Listing 16 |
The output
The output produced by Listing 16 is shown in Figure 16.
Buffer data for buf2 1 2 20 21 22 0 0 99 0 0 Buffer data for buf3 1 2 20 21 22 0 0 99 0 0 Figure 16 |
As you can see, both buffers now contain the value 99 in position 7.
Slicing a buffer
Listing 17 creates a new buffer, which contains a slice of the
data contained in the buffer referred to by buf2.
System.out.println("Slice");
buf2.position(3);
ByteBuffer buf4 = buf2.slice();
buf2.position(0);
showBufferData(buf2, "buf2");
buf4.position(0);
showBufferData(buf4, "buf4");
Listing 17
|
The slice method
Invoking the slice method on a buffer creates a new buffer whose content is a shared subsequence of the original buffer's content.
The content of the new buffer starts at the original buffer's position at the time the slice method was invoked.
As was the case with the duplicate method, the content of the new buffer is linked to the content of the original buffer. Changes to the original buffer's content will be reflected in the new buffer, and vice versa. However, position, limit, and mark values for the two buffers are independent of one another.
The new buffer's initial position is zero. Its capacity and its limit is the number of bytes remaining in the original buffer. Its mark is undefined.
The new buffer will be direct if the original buffer is direct, and it will be read-only if the original buffer is read-only.
Note that the position of the original buffer was set to 3 in Listing 12. Therefore the new buffer should contain all of the elements from element 3 to the limit.
The output
Figure 17 shows the output produced by the code in Listing 17.
Slice Buffer data for buf2 1 2 20 21 22 0 0 99 0 0 Buffer data for buf4 21 22 0 0 99 0 0 Figure 17 |
As you can see, the seven elements in the new buffer (buf4) consist of the seven boldface elements in the original buffer (buf2).
Linked buffers
Listing 18 demonstrates that the contents of the new buffer are linked
to the contents of the buffer of which it is a slice.
buf4.put(5,(byte)66); buf2.position(0); showBufferData(buf2, "buf2"); buf4.position(0); showBufferData(buf4, "buf4"); Listing 18 |
Listing 18 changes the value of the element at position 5 in
the new buffer from zero to 66, and then displays the contents of both
buffers. The output is shown in Figure 18.
Buffer data for buf2 1 2 20 21 22 0 0 99 66 0 Buffer data for buf4 21 22 0 0 99 66 0 Figure 18 |
As you can see, this change to the new buffer is reflected in the value of the corresponding element in the original buffer from which the new buffer is a slice. Note, however, that the element that is changed in the original buffer is the element from which the changed element in the new buffer was extracted. In this case, a change to the element at position 5 in the new buffer was reflected by a change in the element at position 8 in the original buffer.
Direct buffers
All of the buffers used thus far have been non-direct buffers. As mentioned earlier, a buffer is either direct or non-direct. The difference between the two is rather complex, so I will only scratch the surface in describing the differences. For additional information, you are encouraged to review the topic in the Sun documentation for the ByteBuffer class.
This information intended solely to expose the salient points regarding direct buffers.
Avoid copying buffers
The Java virtual machine will make a best effort to perform native I/O operations directly upon a direct buffer. According to Sun, the JVM "will attempt to avoid copying the buffer's content to (or from) an intermediate buffer before (or after) each invocation of one of the underlying operating system's native I/O operations."
Possible garbage collector problems
Also, according to Sun, "The contents of direct buffers may reside outside of the normal garbage-collected heap, and so their impact upon the memory footprint of an application might not be obvious. It is therefore recommended that direct buffers be allocated primarily for large, long-lived buffers that are subject to the underlying system's native I/O operations. In general it is best to allocate direct buffers only when they yield a measurable gain in program performance."
Mapping a file to memory
A direct byte buffer may also be created by mapping a region of a file directly into memory. I plan to discuss this possibility in a future lesson when I discuss the FileChannel class.
Create a direct buffer
The code in Listing 19 creates a 25-element direct buffer by
invoking the allocateDirect factory method of the ByteBuffer
class.
ByteBuffer buf5 =
ByteBuffer.allocateDirect(25);
System.out.println(
"Buffer is direct: "
+ buf5.isDirect());
System.out.println("Order = "
+ buf5.order());
showBufferData(buf5, "buf5");
if(buf5.hasArray())
showArrayData(buf5.array());
else System.out.println(
"No backing array");
Listing 19
|
Invocation of the allocateDirect method allocates a new direct byte buffer. The new buffer's position is zero, its limit is its capacity, and its mark is undefined.
Does the buffer have a backing array?
According to Sun, "Whether or not it has a backing array is unspecified." The code in Listing 19 checks to see if this buffer has a backing array by invoking hasArray method on the new buffer object. When we view the output, we will see that the answer is no when this program is executed on my machine.
The byte order
The next set of operations that we will investigate involves storing data of other primitive types in a buffer of type ByteBuffer. If we were populating the buffers from a source outside of Java, such as a disk file, we might be interested in the order of the bytes. The code in Listing 19 gets and displays the byte order the new buffer.
According to Sun, the ByteBuffer class "defines methods for reading and writing values of all other primitive types, except boolean. Primitive values are translated to (or from) sequences of bytes according to the buffer's current byte order, which may be retrieved and modified via the order methods. Specific byte orders are represented by instances of the ByteOrder class."
BIG_ENDIAN or LITTLE_ENDIAN
The ByteOrder class supports the following byte orders:
- BIG_ENDIAN, in which the bytes of a multibyte value are ordered from most significant to least significant.
- LITTLE_ENDIAN, in which the bytes of a multibyte value are ordered from least significant to most significant.
According to Sun, "This method is defined so that performance-sensitive Java code can allocate direct buffers with the same byte order as the hardware. Native code libraries are often more efficient when such buffers are used."
Also according to Sun, " The initial order of a byte buffer is always BIG_ENDIAN."
Beyond the scope of this lesson ...
Beyond this brief discussion, the topic of byte order for multi-byte values is beyond the scope of this lesson. If you would like to see about 8,000 online references to byte order, go to http://www.google.com/ and search for BIG_ENDIAN.
The output
The code in Listing 19 invokes the order method on the new buffer to get and display the order of the buffer. The code also invokes the isDirect method to confirm that the new buffer is a direct buffer.
Figure 19 shows the output produced by the fragment of code in Listing
19.
Buffer is direct: true Order = BIG_ENDIAN Buffer data for buf5 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 No backing array Figure 19 |
The output confirms that the buffer:
- Is a direct buffer
- Is in BIG_ENDIAN order
- Has 25 elements, each with an initial value of zero
- Has no backing array
Most of the operations that I have discussed so far can be performed on objects instantiated from any of the classes in the following list (subclasses of Buffer):
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
As I mentioned earlier in this lesson, the ByteBuffer class also provides several operations that are not available for the other classes in the above list. I will begin explaining those operations at this point.
Encapsulating other primitive types
In addition to encapsulating data of type byte, an object of the ByteBuffer class can also encapsulate a mixture of all of the other primitive types except for boolean.
For access to sequences of values of different types, the ByteBuffer class defines a family of absolute and relative get and put methods for each type.
Index is by bytes and not by type
It is important to note however, that the index parameters of the absolute get and put methods are in terms of bytes rather than in terms of the type being read or written.
(For example, one value of type int requires four bytes in a byte buffer, so an int value that begins at index zero ends at index 3. The next value of some type would need to begin at index 4.)Invoke relative putDouble method
Listing 20 illustrates storing a primitive double value in a
buffer of type ByteBuffer.
buf5.position(0);
//...print statement deleted
System.out.println(
"Put a double relative");
buf5.putDouble(1.0/3.0);
System.out.println("position = "
+ buf5.position());
Listing 20
|
The code in Listing 20 starts by setting the position to zero. This specifies where the putDouble method will begin storing the sequence of bytes that represent the double value (a double requires eight bytes).
The relative putDouble method
The relative putDouble method is invoked to store a double value in the buffer beginning at position. Invocation of the method writes eight bytes containing the given double value, in the current byte order, into the buffer at the current position, and then increments the position by eight.
The output
Then the code in Listing 20 gets and displays the new value of position.
Figure 20 shows the output produced by the code fragment in Listing
20.
Put a double relative position = 8 Figure 20 |
As you can see, the new value for position is 8.
Store a float and a long
The code in Listing 21 stores two more primitive values, a float,
and a long, by invoking the appropriate relative put method
for the type of data being stored.
System.out.println(
"Put a float relative");
buf5.putFloat((float)(1.0/6.0));
System.out.println("position = "
+ buf5.position());
System.out.println(
"Put a long relative");
buf5.putLong(Long.MAX_VALUE);
System.out.println("position = "
+ buf5.position());
Listing 21
|
In each case, the code in Listing 21 gets and displays the new value of position after storing the value.
The output
Figure 21 shows the output produced by the code in Listing 21.
Put a float relative position = 12 Put a long relative position = 20 Figure 21 |
There should be no surprises in Figure 21. In each case, the value of position was incremented by the number of bytes required to store the value.
Get and display primitive values
The code in Listing 22 sets position to zero, and then invokes
the appropriate relative get method to get and display each of the
primitive values stored earlier in Listings 20 and 21.
buf5.position(0);
System.out.println(
"Get double relative");
System.out.println(
buf5.getDouble());
System.out.println("position = "
+ buf5.position());
System.out.println(
"Get float relative");
System.out.println(
buf5.getFloat());
System.out.println("position = "
+ buf5.position());
System.out.println(
"Get long relative");
System.out.println(buf5.getLong());
System.out.println("position = "
+ buf5.position());
Listing 22
|
Note that for data of mixed types, the program must know where to begin, and must know the type order in which to extract the values.
The relative getDouble method
The relative getDouble method, which is representative of similar methods of all the non-boolean types, reads the next eight bytes at the buffer's current position, composing them into a double value, according to the current byte order. Then it increments the position by eight.
The output
Figure 22 shows the output produced by the code in Listing 22.
Get double relative 0.3333333333333333 position = 8 Get float relative 0.16666667 position = 12 Get long relative 9223372036854775807 position = 20 Figure 22 |
Again, there should be no surprises in Figure 22. In each case, the value stored earlier is retrieved and the position is incremented by the number of bytes appropriate for the type involved.
Perhaps the biggest problem to be faced with using this capability of mixed types is keeping track of what type is stored in which set of bytes so that they can be successfully retrieved later.
If you would like to see the effect of having a mix-up in byte order, insert the following statement after the put code in Listing 21 and before the get code in Listing 22. This will cause the multibyte values to be stored as BIG_ENDIAN and retrieved as LITTLE_ENDIAN. Then examine the values that are displayed to see if they make sense.Absolute get and put methods for other typesbuf5.order(ByteOrder.LITTLE_ENDIAN);
In addition to the relative get and put methods discussed above, the ByteBuffer class also provides absolute get and put methods for all primitive types other than boolean.
Listing 23 uses the absolute version of the getFloat method
to get and display the float value stored earlier in Listing 21.
System.out.println(
"Get float absolute");
System.out.println(
buf5.getFloat(8));
System.out.println("position = "
+ buf5.position());
Listing 23
|
Don't use and don't modify the position property
Note that the absolute versions of this family of methods don't use the value of position to read, and don't modify the value of position. Rather, the position from which the bytes should be read is provided by an incoming index parameter.
Listing 23 gets and displays the value of position after getting and displaying the float value beginning at element index 8.
The output
Figure 23 shown the output produced by the code in Listing 23.
Get float absolute 0.16666667 position = 20 Figure 23 |
Note that the value of the position property is still 20, which is the value established earlier by the code in Listing 22.
Put and get absolute for type int
Listing 24 illustrates the use of the absolute putInt
method to put a primitive int value into the buffer beginning
at element index 20.
System.out.println(
"Put int absolute");
buf5.putInt(20,Integer.MAX_VALUE);
System.out.println(
"Get int absolute");
System.out.println(
buf5.getInt(20));
System.out.println("position = "
+ buf5.position());
Listing 24
|
Then the code uses the absolute getInt method to get and display the int value stored at that location.
Figure 24 shows the output produced by the code in Listing 24.
Put int absolute Get int absolute 2147483647 position = 20 Figure 24 |
By now, you should be able to understand this code, and the output that it produces with no further discussion on my part.
Time for a break
If you haven't taken a break since you first started reading this lesson, this would be a good time to do so. We are getting ready to switch gears and move in a somewhat different direction.
Working with views
Another major capability is provided by the ByteBuffer class for the case where the buffer contains types other than byte or boolean, and where all of the data stored in the buffer is of the same type.
Methods to create view buffers
The ByteBuffer class defines methods to create views of a given buffer, when all of the data in the buffer is of the same type.
(Actually, all the data in the buffer doesn't have to be of the same type, but all of the data between the current position and the limit must be of the same type in order for the results to be meaningful.)A view buffer is another buffer (of a type other than ByteBuffer) whose content is backed by the buffer of type ByteBuffer. The data contained in the view buffer is the data contained in the byte buffer, between position and limit, when the view buffer is created.
A backing buffer
Changes made to the byte buffer's content will be reflected in the view buffer (assuming that the change is made to an element that is common between the two), and changes made to the view buffer will be reflected in the byte buffer. However, the values of the two buffers' position, limit, and mark properties are independent of one another.
Advantages of a view buffer
A view buffer is indexed in terms of the type-specific size of its values instead of being indexed in terms of the number of bytes.
A view buffer provides relative get and put methods for transferring contiguous sequences of values (for types other than byte and boolean) between a buffer and an array or some other buffer of the same type.
According to Sun, "A view buffer is potentially much more efficient because it will be direct if, and only if, its backing byte buffer is direct."
Prepare some data in a buffer
To begin working with views, we first need to put some data, all of
the same type, in a byte buffer. The code in Listing 25 begins by
clearing one of our existing byte buffers.
buf5.clear(); showBufferProperties(buf5, "buf5"); buf5.putInt((int)1); buf5.putInt((int)2); buf5.putInt((int)4); buf5.putInt((int)8); buf5.putInt((int)16); buf5.putInt((int)32); buf5.position(0); System.out.println( "Raw byte data"); showBufferData(buf5, "buf5"); Listing 25 |
The clear method
As the name of the method implies, when the clear method is invoked on a buffer, the position is set to zero, the limit is set to the capacity, and the mark is discarded.
However, according to Sun, "This method does not actually erase the data in the buffer, but it is named as if it did because it will most often be used in situations in which that might as well be the case."
After the buffer is cleared, the code in Listing 25 invokes the showBufferProperties method to confirm that the values of the properties are as explained above.
Invoke the relative putInt method
Then the code in Listing 25 invokes the putInt method six times in succession to store six different int values in the buffer.
Recall that an int value in Java is stored in 32 bits (four bytes) in two's complement notation. The int values stored in the buffer in Listing 25 were chosen so that each of the six values was small enough to reside in the least significant byte of its four-byte group. You will see why I did this in Figure 25, which shows the output produced by the code in Listing 25.
The output
After storing the six int values in the buffer, (which requires
24 bytes), the code in Listing 25 displays the buffer in raw byte format.
The output is shown in Figure 25.
Buffer Properties for buf5 capacity=25 limit=25 position=0 Raw byte data Buffer data for buf5 0 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 16 0 0 0 32 0 Figure 25 |
For purposes of this presentation, I colored each of the four-byte groups that constitute the six int values in alternating colors of red and blue. You should be able to correlate the values in those bytes with the int values stored in the buffer by the code in Listing 25.
The final byte in the buffer was not used in the storage of the six int values. Therefore, I left it black.
ByteBuffer is indexed on bytes
Even though it is possible to store primitive values other than byte in a buffer of type ByteBuffer, it isn't always easy to work with them later because the buffer is always indexed on the number of bytes.
For example, we would like to be able to iterate on the buffer to gain access to each primitive int value in succession. We saw earlier (in the method named showBufferData) that it is very to use the hasRemaining method and the relative get method to iterate on a byte buffer containing data of type byte.
This approach doesn't always work
However, an attempt to use that same approach when the type of data
is something other than byte only works if the capacity of
the buffer is an even multiple of the byte size of the data type stored
in the buffer. For example, the code shown in Listing 26 is similar
to the showBufferData method (with get replaced by getInt)
but it won't work properly in this case.
buf5.position(0);
showBufferProperties(buf5, "buf5");
/******
while(buf5.hasRemaining()){
System.out.print(
buf5.getInt() + " ");
}//end while loop
******/
Listing 26
|
The code in Listing 26 throws an exception after retrieving each of the six int values. (Therefore it was necessary for me to put it in comments to cause the program to run successfully without throwing an exception.)
The problem is that the six int values only consume 24 of the 25 bytes in the buffer. After all six of the int values have been read, the 25th byte is left dangling. This causes the hasRemaining method to return true when in fact, there are no more int values remaining in the buffer. This causes the getInt method to be invoked one more time, and an attempt to invoke getInt at this point causes an exception to be thrown.
A more complex approach
Of course, it is possible to iterate over the int values stored
in the buffer using more complex code, as shown in Listing 27.
int cnt = 0;
while(cnt < buf5.limit()-4){
System.out.print(
buf5.getInt(cnt) + " ");
cnt += 4;
}//end while loop
Listing 27
|
However, it would be nice if a simpler approach were available (and a view provides the simpler approach).
The output
Figure 27 shows the output produced by the code in Listing 27.
1 2 4 8 16 32 Figure 27 |
As you might expect, the code in Listing 27 displays the six int values stored in the buffer by the code in Listing 25.
Get a view buffer
The code in Listing 28 invokes the asIntBuffer method on the
byte buffer to create a view buffer of type IntBuffer.
buf5.position(0);
IntBuffer buf6 =
buf5.asIntBuffer();
showBufferProperties(buf6, "buf6");
System.out.println("Read-only = "
+ buf6.isReadOnly());
Listing 28
|
The asIntBuffer method
Invocation of asIntBuffer on a byte buffer creates a view object of the byte buffer as type IntBuffer (another subclass of Buffer). This causes the methods of the IntBuffer class to become available for performing operations on the new view buffer.
(Note that the invocation of asIntBuffer on a byte buffer only makes sense when the byte buffer contains a sequence of int values between the current value of the position property and the value of the limit property. However, I don't believe there are any safety nets to keep you from invoking the asIntBuffer method on a byte buffer even when it isn't appropriate. You won't get a compiler error, and you probably won't get a runtime error either, unless there is a buffer overflow or buffer underflow. Viewing the data through the view buffer simply won't make any sense. From the viewpoint of safety, these new programming capabilities are more like programming in C or C++ than programming in Java.)The view buffer
The content of the view buffer starts at the byte buffer's current position.
Once the view buffer has been created, changes to the byte buffer's content will be reflected in the view buffer, and vice versa. However, the values of the two buffers' position, limit, and mark properties will be independent.
The view buffer's initial position is zero. Its capacity and its limit are the number of bytes remaining in the byte buffer divided by four (for type int). Its mark is undefined.
The view buffer will be direct if the byte buffer is direct, and it will be read-only if the byte buffer is read-only.
The output
After creating the view buffer, the code in Listing 28 displays its
properties and checks to see if it is a read-only buffer. Figure
28 shows the output produced by the code in Listing 28.
Buffer Properties for buf6 capacity=6 limit=6 position=0 Read-only = false Figure 28 |
The capacity and the limit for the view buffer are equal to the number of int values reflected in the buffer, and the position is set to zero.
Iterate on the view buffer
The code in Listing 29 uses the relative get method of the IntBuffer
class, along with the hasRemaining method of the Buffer class
to get and to display each of the int values reflected in the view
buffer.
while(buf6.hasRemaining()){
System.out.print(
buf6.get() + " ");
}//end while loop
Listing 29
|
Note the simplicity of the code in Listing 29, as compared to the code in Listing 27.
The output
The output produced by the code in Listing 29 (shown in Figure 29)
is exactly the same as the output produced by the more complex code in
Listing 27.
1 2 4 8 16 32 Figure 29 |
In both cases, the code gets and displays the six int values stored in the buffer of type ByteBuffer. However, the code in Listing 29 operates on a view buffer of type IntBuffer, whereas the code in Listing 27 operates on the original buffer of type ByteBuffer.
Get an int value with the absolute get method
Listing 30 illustrates another major advantage of using a view buffer.
System.out.println("Element 4 = "
+ buf6.get(4));
Listing 30
|
The code in Listing 30 gets and displays the int value at position 4 in the view buffer. Note that when invoking the absolute get method on the view buffer, it isn't necessary to take into account the number of bytes required to form each int value. In other words, the view buffer of type IntBuffer is indexed by byte groups (where each group consists of the number of bytes required to store a value of type int) and not by raw byte numbers.
To access the same int value from the byte buffer, it would be necessary to invoke the getInt method on the buffer and to specify the proper index, taking into account that each int value requires four bytes.
Figure 30 shows the output produced by the code in Listing 30.
Element 4 = 16 Figure 30 |
You can confirm that this is correct by comparing the value with that shown in Figure 29.
Using absolute put method on the view object
New int values can be stored in the underlying buffer of type
ByteBuffer
by invoking the absolute put method on the view object. This
is illustrated in Listing 31, which invokes the absoluteput
method on the view object to store the int value 99 at
position
4.
buf6.put(4,99);
buf6.position(0);
//...print statement deleted
while(buf6.hasRemaining()){
System.out.print(
buf6.get() + " ");
}//end while loop
Listing 31
|
After storing the new value, the code in Listing 31 iterates on the
view object to display the contents of the shared byte buffer, as reflected
in the view object. Figure 31 shows the output produced by the code
in Listing 31.
1 2 4 8 99 32 Figure 31 |
Note that the int value of 16 previously stored in position 4 has now been replaced by an int value of 99.
Show the backing buffer
Keep in mind that changes made to the view are also reflected in the backing buffer, which is the original buffer of type ByteBuffer.
The code in Listing 32 displays the contents of the original buffer
of type ByteBuffer in raw byte form, twelve bytes per line.
buf5.position(0); showBufferData(buf5, "buf5"); Listing 32 |
Figure 32 shows the output from the code in Listing 32.
Buffer data for buf5 0 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 99 0 0 0 32 0 Figure 32 |
Once again, I have colored the four-byte groups that constitute the six int values in alternating colors of red and blue. Note in particular the boldface red group that now contains the value 99 in the least significant byte. If you refer back to Figure 25, you will see that the least significant byte of this group contained 16 before the change was made to the view object.
A read-only view of type byte
Each of the seven subclasses of the Buffer class provide a method
named asReadOnlyBuffer, which can be used to create a read-only
view buffer for that buffer type. This is illustrated in Listing
33.
buf5.position(0);
ByteBuffer buf7 =
buf5.asReadOnlyBuffer();
System.out.println("Read-only = "
+ buf7.isReadOnly());
Listing 33
|
The asReadOnlyBuffer method
Invocation of the asReadOnlyBuffer method on a buffer creates a new, read-only buffer that shares the original buffer's content.
The content of the new buffer is the same as the content of the original buffer (regardless of the value of position when the asReadOnlyBuffer method is invoked).
Changes to the original buffer's content are reflected in the new buffer. However, the new buffer is read-only and its content cannot be modified. Therefore, the shared content cannot be modified by making changes to the read-only buffer. The values of the two buffers' position, limit, and mark properties are independent of one another
The new buffer's initial capacity, limit, position, and mark values are identical to those of the original buffer.
The output
Figure 33 shows the output produced by the code in Listing 33.
Read-only = true Figure 33 |
As you can see from Figure 33, invocation of the isReadOnly method on the new buffer confirms that it is a read-only buffer.
Compare the two buffers
The code in Listing 34 displays the contents of the original buffer
and the read-only view buffer to confirm that they have the same contents.
System.out.println( "Show backing buffer"); showBufferData(buf5, "buf5"); System.out.println( "Show view data"); showBufferData(buf7, "buf7"); Listing 34 |
The output shown in Figure 34 confirms that the two buffers do have
the same contents.
Show backing buffer Buffer data for buf5 0 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 99 0 0 0 32 0 Show view data Buffer data for buf7 0 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 99 0 0 0 32 0 Figure 34 |
Modify the backing buffer
The code in Listing 35 modifies the value in the first byte of the original
backing buffer, and then displays the contents of both buffers to confirm
that changes to the original buffer are reflected in the read-only view
buffer.
buf5.put(0,(byte)66); System.out.println( "Show backing buffer"); buf5.position(0); showBufferData(buf5, "buf5"); System.out.println( "Show view data"); buf7.position(0); showBufferData(buf7, "buf7"); Listing 35 |
Figure 35 shows the output produced by the code in Listing 35, and does
confirm that changes made to the backing buffer are reflected in the view
buffer.
Show backing buffer Buffer data for buf5 66 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 99 0 0 0 32 0 Show view data Buffer data for buf7 66 0 0 1 0 0 0 2 0 0 0 4 0 0 0 8 0 0 0 99 0 0 0 32 0 Figure 35 |
In particular, when the value in the first byte in the original buffer was changed to 66, this new value was reflected in the first byte of the view buffer.
Modify the view buffer - oops, not possible
An attempt to execute the statement shown as a comment in Listing 36
causes the program to throw an exception
//buf7.put(0,(byte)66); Listing 36 |
This confirms that this view buffer really is a read-only buffer.
That's it for now
By now you should understand a quite a lot about the ByteBuffer class in the new java.nio package.
Future articles will discuss channels and other new I/O features introduced in Java version 1.4.0. Those discussions will depend heavily on an understanding of the Buffer class and its subclasses, including ByteBuffer.
