Published: August 20, 2002
By Richard G. Baldwin
Java Programming Notes # 1782
The recently released JavaTM 2 SDK, Standard Edition Version 1.4.0 contains a number of new features. This article explains how to use some of those new features.
Among the new features is a new I/O API. Here is how Sun describes that API and the new features that it provides:
"The new I/O (NIO) APIs introduced in v 1.4 provide new features and improved performance in the areas of buffer management, scalable network and file I/O, character-set support, and regular-expression matching. The NIO APIs supplement the I/O facilities in the java.io package."Basic classes
The abstract Buffer class, and its subclasses, are basic to many of the new features in the NIO. The Sun documentation lists the following known subclasses of Buffer:
The purpose of this lesson is to help you understand how to use the features of the ByteBuffer class. I will describe many of those features, and will illustrate the use of those features by explaining the code in a sample program.
Caution: Dangerous Curves Ahead
A caution is in order regarding the capabilities discussed in this lesson. Once you enter the domain of the ByteBuffer class, you have left the type-safe world normally associated with Java behind. You have entered a domain more akin to that normally enjoyed by adventuresome C and C++ programmers.
For example, there is nothing to prevent you from creating a buffer for byte data, populating it with double data, and then erroneously viewing and interpreting it as type int. Also, there is nothing to prevent you from interpreting LITTLE_ENDIAN data as BIG_ENDIAN, and vice versa. There are many other ways that you can go astray as well, and neither the compiler nor the virtual machine are of much help in preventing such programming errors.
I won't spend a lot of time discussing these matters, but I have provided a short sample program that illustrates the above sequence of events in Listing 38 near the end of the lesson.
Viewing tip
You may find it useful to open another copy of this lesson in a separate browser window. That will make it easier for you to scroll back and forth among the different listings and figures while you are reading about them.
Supplementary material
I recommend that you also study the other lessons in my extensive collection of online Java tutorials. You will find those lessons published at Gamelan.com. However, as of the date of this writing, Gamelan doesn't maintain a consolidated index of my Java tutorial lessons, and sometimes they are difficult to locate there. You will find a consolidated index at www.DickBaldwin.com.
As mentioned above, the class named ByteBuffer extends the abstract class named Buffer. The Sun documentation lists the following known subclasses of Buffer:
As you can see from the names of the subclasses, there is one subclass of the Buffer class for each non-boolean primitive type. If you compare the documentation for the seven subclasses of Buffer, you will find that they are very similar. In fact, each of the subclasses provides the following four capabilities for data of the type whose name appears in the subclass name.
Additional capabilities of ByteBuffer class
The ByteBuffer class also provides the following two additional capabilities. This makes the ByteBuffer class more general than the other five subclasses of Buffer.
Will concentrate on the ByteBuffer class
Therefore, I have singled out the ByteBuffer class for a detailed discussion in this lesson. Once you understand how to use objects of the ByteBuffer class, you should also understand how to use objects instantiated from the other subclasses of Buffer.
A container for primitive data
None of the container classes in the Java Collections Framework are designed to contain primitive data. Those containers are all designed to contain references to objects. Objects instantiated from subclasses of Buffer are containers for primitive data.
Three important properties
Every object instantiated from Buffer has the following three important properties:
Writing and reading data
Subclasses of Buffer use put and get operations to store data into a buffer and to read data from the buffer (to transfer data into and out of the buffer). Each subclass defines two categories of put and get operations: relative and absolute.
Relative put and get operations
Relative data transfer operations store or read one or more elements starting at the current position. The position is automatically incremented based on the number of items transferred and the type of data transferred.
Absolute put and get operations
Absolute data transfer operations take an element index as a parameter and use that index to store or retrieve data. These operations do not affect the value of the position property.
Method chaining
Some of the methods of the ByteBuffer class return a reference
to the buffer. This makes it possible to use method invocation
chaining syntax such as that shown in Figure 1.
buf5.putDouble(1.0/3.0). putFloat((float)(1.0/6.0)). putLong(Long.MAX_VALUE); Figure 1 |
Read-only buffers
It is possible to create read-only buffers, and I will do so in the sample program in this lesson. Methods that normally change the contents of a buffer will throw a ReadOnlyBufferException when invoked on a read-only buffer.
While a read-only buffer does not allow its content to be changed, its mark, position, and limit values may be changed. You can determine if a buffer is read-only by invoking its isReadOnly method.
The sample program named ByteBuffer01
The features of the ByteBuffer class are illustrated in the program named ByteBuffer01, which I will discuss in fragments. A complete listing of the program is provided in Listing 37 near the end of the lesson.
Displaying buffer properties
Listing 1 shows a convenience method that I wrote, whose purpose is
to display the properties of a buffer. The reference to the buffer
of interest and a String to identify the buffer are passed as parameters
to the method.
static void showBufferProperties( Buffer buf,String name){ System.out.println( "Buffer Properties for " + name +"\n capacity=" + buf.capacity() + " limit=" + buf.limit() + " position=" + buf.position()); }//end showBufferProperties Listing 1 |
The method uses the three getter methods of the Buffer class to get and display the values of the following properties:
The format of the output produced by Listing 1 is illustrated in Figure
2.
Buffer Properties for buf5 capacity=25 limit=25 position=0 Figure 2 |
Display byte data in the buffer
Listing 2 shows a convenience method designed to display the byte data
stored in the buffer, from the current value of the position property
to the value of the limit property. The byte data is displayed
12 bytes per row of output.
static void showBufferData( ByteBuffer buf, String name){ System.out.println( "Buffer data for " + name); int cnt = 0; while(buf.hasRemaining()){ System.out.print( buf.get() + " "); cnt++; if(cnt%12 == 0) System.out.println();//line }//end while loop System.out.println();//blank line }//end showBufferData Listing 2 |
The code in Listing 2 invokes two important methods that are new to version 1.4.0 of the SDK:
The hasRemaining method is much like one of the methods of the Iterator and Enumeration interfaces, which are used to iterate on objects instantiated from the concrete classes of the Java Collections Framework.
The hasRemaining method tells whether there are any elements remaining between the current position and the limit. The method returns a boolean, which is true only if there is at least one element remaining in the buffer. Thus, this method works very nicely in the conditional clause of a while loop for the purpose of iterating on a buffer.
The relative get method
The relative get method of the ByteBuffer class reads and returns the byte at the buffer's current position, and then increments the position. Thus, it also works quite well in an iterator loop for a buffer (provided you have exercised proper control over the values of the position and limit properties beforehand).
The format of the output produced by the code in Listing 2 is illustrated
in Figure 3. Each numeric value in figure three is the value of a
single byte, and the values for twelve bytes are displayed on each row
of output.
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 3 |
Display array data
Listing 3 is a convenience method designed simply to display the data
in an array object of type byte.
static void showArrayData( byte[] array){ System.out.println( "Show array data"); for(int cnt = 0; cnt < array.length; cnt++){ System.out.print( array[cnt] + " "); if((cnt+1)%12 == 0) System.out.println();//line }//end for loop System.out.println();//blank line }//end showArrayData Listing 3 |
I am assuming that you are already familiar with the use of array objects in Java, and therefore, I won't discuss this code in detail. If that is not the case, you can learn about array objects at www.DickBaldwin.com.
Create an array object
There are several ways to create a buffer object in Java. One of those ways is to wrap an existing array object in a buffer object. To do that, we need an array object, which I will create using the code in Listing 4. (I will discuss two other ways to create a buffer object later in this lesson.)
Listing 4 shows the beginning of the main method. The code
in Listing 4 creates, populates, and displays an eight-element array object
containing data of type byte.
public static void main( String[] args){ //Wrap a byte array into a buffer System.out.println( "Create and populate array"); byte[] a1 = {0,1,2,3,4,5,6,7}; showArrayData(a1); Listing 4 |
Again, I am assuming that you are already familiar with the use of array
objects in Java, and therefore, I won't discuss this code in detail.
The code in Listing 4 produces the output shown in Figure 4.
Create and populate array Show array data 0 1 2 3 4 5 6 7 Figure 4 |
I show this here because we will want to compare it with the data stored in our buffer object later.
Create a ByteBuffer object
As mentioned above, there are several ways to create a buffer, and one
of them is shown in Listing 5.
System.out.println( "Wrap byte array in buffer"); ByteBuffer buf1 = ByteBuffer.wrap(a1); System.out.println( "Buffer is direct: " + buf1.isDirect()); showBufferData(buf1, "buf1"); Listing 5 |
Listing 5 invokes the static wrap method of the ByteBuffer class to create a buffer that wraps the existing array object referred to by the reference variable named a1.
Wrapping an array object
There are two overloaded versions of the wrap method, one that requires incoming offset and length parameters, and one that doesn't. (Both versions require an incoming reference to an array object.) I used the simpler of the two versions, which does not require offset and length.
A backing array
For both versions, the new buffer is backed up by, or connected to, the byte array, which it wraps. Modifications to the buffer cause the array contents to be modified, and modifications to the array cause the buffer contents to be modified. (It appears as though they are really the same set of data. Note that this is a common theme that will arise more than once in this lesson.)
Buffer property values
For the version of the wrap method that I used, the capacity and limit properties of the new buffer are the same as array.length. The initial value of the position property of the new buffer is zero, and its mark is undefined.
For the more complex version, the initial values of the buffer properties are determined by the values of the offset and length parameters passed to the wrap method.
Direct vs. non-direct buffers
The code in Listing 5 also introduces you to the fact that a byte buffer
is either direct or non-direct. I will have more to say about
this later. For now, suffice it to say that a buffer created by wrapping
an array object is non-direct, as indicated by the program output
shown in Figure 5.
Wrap byte array in buffer Buffer is direct: false Buffer data for buf1 0 1 2 3 4 5 6 7 Figure 5 |
Figure 5 also shows the byte contents of the buffer. If you compare this with the array contents shown in Figure 4, you will see that the buffer and its backing array contain the same values.
Modifications are reflected ...
Modifications to the buffer cause the array contents to be modified,
and modifications to the array cause the buffer contents to be modified.
This is partially illustrated in Listing 6.
System.out.println( "Modify first array element"); a1[0] = 10; showArrayData(a1); buf1.position(0); showBufferData(buf1, "buf1"); Listing 6 |
The code in Listing 6 changes the value in the first array element from 0 to 10, and then displays the modified contents of the array object and the buffer. You will see that this causes the value of the first element in the buffer to change accordingly.
Set the position to zero
Recall that the showBufferData method described earlier (and invoked in Listing 6) displays the contents of the buffer from the element specified by the value of the position property, to the element specified by the value of the limit property.
At this point, (and at numerous other points in this program), it is necessary to set the value of the position property to zero before invoking showBufferData. Otherwise, the contents of the entire buffer would not be displayed. This is accomplished by invoking the position method inherited from the Buffer class and passing zero as a parameter.
Figure 6 shows the output produced by the code in Listing 6.
Modify first array element Show array data 10 1 2 3 4 5 6 7 Buffer data for buf1 10 1 2 3 4 5 6 7 Figure 6 |
The important thing to note in Figure 6 is that the value in the first element of the buffer was changed when the value in the first element of the backing array was changed.
Absolute and relative put methods
As I explained earlier, the ByteBuffer class provides both absolute
and relative versions of the put and get methods.
Listing 7 illustrates both categories of put methods. In addition,
Listing 7 also illustrates the fact that modifications to the buffer cause
the array contents to be modified accordingly.
System.out.println( "Modify the buffer"); buf1.put(3,(byte)20); buf1.position(4); buf1.put((byte)21); buf1.put((byte)22); buf1.position(0); showBufferData(buf1, "buf1"); showArrayData(a1); Listing 7 |
Invoke absolute put method
The code in Listing 7 begins by using the absolute version of the put method to write the byte value 20 into the buffer at position 3. This overwrites the value previously stored at that position. (Note that the invocation of the absolute version of the put method has no effect on the value of the position property.)
Invoke relative put method
Then the code in Listing 7 sets the value of the position property to 4, and invokes the relative version of the put method twice in succession. This causes the values 21 and 22 to overwrite the values previously stored in positions 4 and 5.
Display the data
Then Listing 7 sets the position to 0 and displays the contents
of the buffer, followed by the contents of the array. The output
produced by Listing 7 is shown in Figure 7.
Modify the buffer Buffer data for buf1 10 1 2 20 21 22 6 7 Show array data 10 1 2 20 21 22 6 7 Figure 7 |
Perhaps the most important things to observe in this output are:
The relative get method is illustrated in the showBufferData
method discussed earlier. The absolute get method is
illustrated in Listing 8.
System.out.println("Get absolute"); System.out.println("Element 3 = " + buf1.get(3)); System.out.println("Element 5 = " + buf1.get(5)); System.out.println("Element 7 = " + buf1.get(7)); Listing 8 |
The code in Listing 8 gets and displays the values stored in positions
3, 5, and 7. The output produced is shown in Figure 8.
Get absolute Element 3 = 20 Element 5 = 22 Element 7 = 7 Figure 8 |
You can verify these results by comparing them back against the buffer contents shown in Figure 7.
Contiguous get and put operations
In addition to reading and writing single elements from the buffer, the subclasses of the Buffer class (including ByteBuffer) allow for transferring contiguous blocks of elements into and out of the buffer.
The ByteBuffer class provides for the transfer of a block of bytes from the buffer into an array object of type byte (contiguous get). The class also provides for the transfer of a block of bytes from an array object (of type byte), or from another ByteBuffer object, into contiguous elements of a ByteBuffer object (contiguous put).
A contiguous get to an array
Listing 9 illustrates the use of the contiguous get method to
transfer a block of contiguous bytes from the buffer to a new empty array
object of type byte.
System.out.println( "Contiguous get"); buf1.position(0); showBufferData(buf1, "buf1"); byte[] a2 = new byte[10]; showArrayData(a2); buf1.position(1); buf1.get(a2, 3, 5); showArrayData(a2); Listing 9 |
Create a new byte array
The code in Listing 9 begins by displaying the contents of the buffer for later comparison with the array contents. Then it creates a new array object of type byte whose length is 10. This is larger than the capacity of the buffer. (Recall that the initial value of each of the elements in a new byte array is zero unless purposely initialized using the syntax shown in Listing 4.)
Invoke the contiguous get method
Then the code in Listing 9 sets the position of the buffer to 1, and invokes the get method, passing the following parameters:
Throwing exceptions
As you have probably figured out already, various conditions involving the specified number of bytes, the remaining number of bytes in the buffer, and the length of the array can conflict, causing exceptions to be thrown. I won't attempt to explain those conditions here, but will simply refer you to the Sun documentation for those details.
Display the data
Finally, the code in Listing 9 displays the contents of the array into which the bytes were transferred.
The output produced by this section of the program is shown in Figure
9.
Contiguous get Buffer data for buf1 10 1 2 20 21 22 6 7 Show array data 0 0 0 0 0 0 0 0 0 0 Show array data 0 0 0 1 2 20 21 22 0 0 Figure 9 |
As you can see in Figure 9, after the transfer takes place, there are five non-zero values in the array, beginning at index 3. This matches the array offset value of 3 and the specified length of 5 passed as parameters to the contiguous get method.
Also, as you can see in Figure 9, the five values transferred from the buffer to the array began at position 1 in the buffer, corresponding to the fact that the position was set to 1 immediately prior to the invocation of the get method.
Contiguous put from array as source of data
Listing 10 illustrates the transfer of a block of bytes from an array
into contiguous elements in a buffer.
System.out.println( "Contiguous put from array"); showArrayData(a2); buf1.position(0); buf1.put(a2, 1, 8); buf1.position(0); showBufferData(buf1, "buf1"); Listing 10 |
The code in Listing 10 begins by displaying the contents of the array for later comparison with the contents of the buffer.
Invoke contiguous put method
Then the code in Listing 10 sets the position of the buffer to 0, and invokes the put method, passing the following parameters:
The output produced by the code in Listing 10 is shown in Figure 10.
Contiguous put from array Show array data 0 0 0 1 2 20 21 22 0 0 Buffer data for buf1 0 0 1 2 20 21 22 0 Figure 10 |
Figure 10 shows that 8 bytes were transferred from the array to the buffer, beginning with the value at array index 1. These eight bytes were written into the buffer beginning at position 0, overwriting the eight values that previously existed in the buffer.
Another non-direct buffer
Here are some of the details from Sun regarding direct and non-direct buffers:
"A byte buffer is either direct or non-direct. ... A direct byte buffer may be created by invoking the allocateDirect factory method of this class. ... Whether a byte buffer is direct or non-direct may be determined by invoking its isDirect method. This method is provided so that explicit buffer management can be done in performance-critical code."I will have more to say about direct buffers later on.
Listing 11 illustrates the creation of a non-direct buffer through
allocation.
ByteBuffer buf2 = ByteBuffer.allocate(10); System.out.println( "Buffer is direct: " + buf2.isDirect()); Listing 11 |
Allocate the buffer
Note that the code in Listing 11 invokes the allocate factory method of the ByteBuffer class (and not the allocateDirect factory method, which would create a direct buffer).
Then, for purpose of illustration, the code in Listing 11 invokes the isDirect method on the new buffer to determine if it is direct or non-direct.
The output
Figure 11 shows the output produced by the code in Listing 11.
Buffer is direct: false Figure 11 |
As you can see from the output, this buffer is non-direct.
Not a wrap
This approach to creating a new buffer is different from the wrapping approach illustrated earlier in the program. This buffer is initially empty, and its capacity is 10 elements (the value passed as a parameter to the factory method). As an empty buffer of type byte, the initial value of each of its elements is zero.
The new buffer's initial position is 0, its limit is the same as its capacity, and its mark is undefined.
Although this buffer was not created by wrapping an existing array, the buffer does have a backing array, and the offset of the backing array is zero (I will have more to say about the backing array later).
Contiguous put from buffer as source of data
This new empty buffer is referred to by the reference variable named
buf2.
Listing 12 illustrates the transfer of a contiguous block of bytes from
the buffer referred to by buf1 to this new buffer.
showBufferData(buf2, "buf2"); buf2.position(1); buf1.position(0); buf2.put(buf1); buf1.position(0); showBufferData(buf1, "buf1"); buf2.position(0); showBufferData(buf2, "buf2"); Listing 12 |
The code in Listing 12 begins by displaying the contents of the new buffer. We will see shortly that each of the elements in the new buffer is initialized to a value of zero.
Set positions and invoke contiguous put method
Then the code in Listing 12 sets position values for each of the buffers and invokes the contiguous put method on buf2, passing buf1 as a parameter. The parameter specifies the buffer that acts as a source for the data to be transferred. The position values of each of the buffers control the data that is actually transferred.
What data is actually transferred?
In particular, this version of the put method transfers the bytes remaining in the source buffer (between position and limit) into the destination buffer on which the method is invoked. The data is transferred into the destination buffer beginning at the current position for the destination buffer. Then the position properties of both buffers are incremented by the number of bytes transferred.
Exceptions can be thrown
Several conditions can cause exceptions to be thrown, but I will simply refer you to the Sun documentation for the details in that regard.
The output
After the data has been transferred, the contents of both buffers are
displayed. The output produced by the code in Listing 12 is shown
in Figure 12.
Buffer data for buf2 0 0 0 0 0 0 0 0 0 0 Buffer data for buf1 0 0 1 2 20 21 22 0 Buffer data for buf2 0 0 0 1 2 20 21 22 0 0 Figure 12 |
As you can see, the initial values of each of the ten elements in the new empty buffer are zero.
All eight bytes of data are transferred from buf1 (beginning at position 0 in buf1) to buf2 (beginning at position 1 in buf2).
(Note that the boldface values don't mean anything special here. I will refer back to them later.)
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:
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:
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):
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.
Remember, however, that you must be running Java version 1.4.0 or later to compile and execute this program.
/* File ByteBuffer01.java Copyright 2002, R.G.Baldwin Illustrates most of the features of the ByteBuffer class. Tested using JDK 1.4.0 under Win 2000. The output is: Create and populate array Show array data 0 1 2 3 4 5 6 7 Wrap byte array in buffer Buffer is direct: false Buffer data for buf1 0 1 2 3 4 5 6 7 Modify first array element Show array data 10 1 2 3 4 5 6 7 Buffer data for buf1 10 1 2 3 4 5 6 7 Modify the buffer Buffer data for buf1 10 1 2 20 21 22 6 7 Show array data 10 1 2 20 21 22 6 7 Get absolute Element 3 = 20 Element 5 = 22 Element 7 = 7 Contiguous get Buffer data for buf1 10 1 2 20 21 22 6 7 Show array data 0 0 0 0 0 0 0 0 0 0 Show array data 0 0 0 1 2 20 21 22 0 0 Contiguous put from array Show array data 0 0 0 1 2 20 21 22 0 0 Buffer data for buf1 0 0 1 2 20 21 22 0 Contiguous put from buffer Buffer is direct: false Buffer data for buf2 0 0 0 0 0 0 0 0 0 0 Buffer data for buf1 0 0 1 2 20 21 22 0 Buffer data for buf2 0 0 0 1 2 20 21 22 0 0 Show array data 0 0 0 1 2 20 21 22 0 0 Compacting Buffer data for buf2 22 0 0 Buffer data for buf2 1 2 20 21 22 0 0 22 0 0 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 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 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 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 Other primitive types 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 Put and get primitive types Put a double relative position = 8 Put a float relative position = 12 Put a long relative position = 20 Get double relative 0.3333333333333333 position = 8 Get float relative 0.16666667 position = 12 Get long relative 9223372036854775807 position = 20 Get float absolute 0.16666667 position = 20 Put int absolute Get int absolute 2147483647 position = 20 Work with views Clear buf5 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 This won't always work More complex approach 1 2 4 8 16 32 Get a view Buffer Properties for buf6 capacity=6 limit=6 position=0 Read-only = false Get relative from view 1 2 4 8 16 32 Get absolute from view Element 4 = 16 Put absolute in view Show modified view 1 2 4 8 99 32 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 Create read-only view Read-only = true 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 Modify backing 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 Modify view oops! **************************************/ import java.nio.*; class ByteBuffer01{ static void showBufferProperties( Buffer buf,String name){ System.out.println( "Buffer Properties for " + name +"\n capacity=" + buf.capacity() + " limit=" + buf.limit() + " position=" + buf.position()); }//end showBufferProperties //---------------------------------// static void showBufferData( ByteBuffer buf, String name){ //Displays buffer contents from // current position to limit using // relative get method, twelve // elements per row. System.out.println( "Buffer data for " + name); int cnt = 0; while(buf.hasRemaining()){ System.out.print( buf.get() + " "); cnt++; if(cnt%12 == 0) System.out.println();//line }//end while loop System.out.println();//blank line }//end showBufferData //---------------------------------// static void showArrayData( byte[] array){ System.out.println( "Show array data"); for(int cnt = 0; cnt < array.length; cnt++){ System.out.print( array[cnt] + " "); if((cnt+1)%12 == 0) System.out.println();//line }//end for loop System.out.println();//blank line }//end showArrayData //---------------------------------// public static void main( String[] args){ //Wrap a byte array into a buffer System.out.println( "Create and populate array"); byte[] a1 = {0,1,2,3,4,5,6,7}; showArrayData(a1); System.out.println();//blank line System.out.println( "Wrap byte array in buffer"); ByteBuffer buf1 = ByteBuffer.wrap(a1); System.out.println( "Buffer is direct: " + buf1.isDirect()); showBufferData(buf1, "buf1"); System.out.println();//blank line //Mods to the buffer will cause the // array to be modified and vice // versa. System.out.println( "Modify first array element"); a1[0] = 10; showArrayData(a1); buf1.position(0); showBufferData(buf1, "buf1"); System.out.println();//blank line System.out.println( "Modify the buffer"); buf1.put(3,(byte)20); buf1.position(4); buf1.put((byte)21); buf1.put((byte)22); buf1.position(0); showBufferData(buf1, "buf1"); showArrayData(a1); System.out.println();//blank line System.out.println("Get absolute"); System.out.println("Element 3 = " + buf1.get(3)); System.out.println("Element 5 = " + buf1.get(5)); System.out.println("Element 7 = " + buf1.get(7)); System.out.println();//blank line System.out.println( "Contiguous get"); buf1.position(0); showBufferData(buf1, "buf1"); byte[] a2 = new byte[10]; showArrayData(a2); buf1.position(1); buf1.get(a2, 3, 5); showArrayData(a2); System.out.println();//blank line System.out.println( "Contiguous put from array"); showArrayData(a2); buf1.position(0); buf1.put(a2, 1, 8); buf1.position(0); showBufferData(buf1, "buf1"); System.out.println();//blank line System.out.println( "Contiguous put from buffer"); ByteBuffer buf2 = ByteBuffer.allocate(10); System.out.println( "Buffer is direct: " + buf2.isDirect()); showBufferData(buf2, "buf2"); buf2.position(1); buf1.position(0); buf2.put(buf1); buf1.position(0); showBufferData(buf1, "buf1"); buf2.position(0); showBufferData(buf2, "buf2"); if(buf2.hasArray()) showArrayData(buf2.array()); System.out.println();//blank line System.out.println("Compacting"); buf2.position(3); buf2.compact(); showBufferData(buf2, "buf2"); buf2.position(0); showBufferData(buf2, "buf2"); System.out.println();//blank line System.out.println("Duplicating"); ByteBuffer buf3 = buf2.duplicate(); buf2.position(0); showBufferData(buf2, "buf2"); buf3.position(0); showBufferData(buf3, "buf3"); buf3.put(7,(byte)99); buf2.position(0); showBufferData(buf2, "buf2"); buf3.position(0); showBufferData(buf3, "buf3"); System.out.println();//blank line System.out.println("Slice"); buf2.position(3); ByteBuffer buf4 = buf2.slice(); buf2.position(0); showBufferData(buf2, "buf2"); buf4.position(0); showBufferData(buf4, "buf4"); buf4.put(5,(byte)66); buf2.position(0); showBufferData(buf2, "buf2"); buf4.position(0); showBufferData(buf4, "buf4"); System.out.println();//blank line //The above operations are common // to most of the subclasses of // Buffer. The following // operations are specific to // ByteBuffer. System.out.println( "Other primitive types"); 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"); buf5.position(0); System.out.println( "Put and get primitive types"); System.out.println( "Put a double relative"); buf5.putDouble(1.0/3.0); System.out.println("position = " + buf5.position()); 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()); System.out.println();//blank line 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()); System.out.println();//blank line System.out.println( "Get float absolute"); System.out.println( buf5.getFloat(8)); System.out.println("position = " + buf5.position()); System.out.println();//blank line 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()); System.out.println();//blank line System.out.println( "Work with views"); System.out.println("Clear buf5"); 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"); System.out.println();//blank line System.out.println( "This won't always work"); //The following works only if the // size of the buffer is a // multiple of the number of bytes // for the type. buf5.position(0); /* showBufferProperties(buf5, "buf5"); while(buf5.hasRemaining()){ System.out.print( buf5.getInt() + " "); }//end while loop */ System.out.println();//blank line System.out.println( "More complex approach"); int cnt = 0; while(cnt < buf5.limit()-4){ System.out.print( buf5.getInt(cnt) + " "); cnt += 4; }//end while loop System.out.println();//blank line System.out.println();//blank line System.out.println("Get a view"); buf5.position(0); IntBuffer buf6 = buf5.asIntBuffer(); showBufferProperties(buf6, "buf6"); System.out.println("Read-only = " + buf6.isReadOnly()); System.out.println();//blank line System.out.println( "Get relative from view"); while(buf6.hasRemaining()){ System.out.print( buf6.get() + " "); }//end while loop System.out.println();//blank line System.out.println( "Get absolute from view"); System.out.println("Element 4 = " + buf6.get(4)); System.out.println();//blank line System.out.println( "Put absolute in view"); buf6.put(4,99); buf6.position(0); System.out.println( "Show modified view"); while(buf6.hasRemaining()){ System.out.print( buf6.get() + " "); }//end while loop System.out.println();//blank line System.out.println( "Show backing buffer"); buf5.position(0); showBufferData(buf5, "buf5"); System.out.println();//blank line System.out.println( "Create read-only view"); buf5.position(0); ByteBuffer buf7 = buf5.asReadOnlyBuffer(); System.out.println("Read-only = " + buf7.isReadOnly()); System.out.println( "Show backing buffer"); showBufferData(buf5, "buf5"); System.out.println( "Show view data"); showBufferData(buf7, "buf7"); System.out.println();//blank line System.out.println( "Modify backing 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"); System.out.println();//blank line System.out.println( "Modify view oops!"); //Following statement throws // exception //buf7.put(0,(byte)66); }// end main }//end class ByteBuffer01 definition Listing 37 |
/* File ByteBuffer02.java Copyright 2002, R.G.Baldwin Illustrates lack of type safety when using views of a ByteBuffer object. Creates a ByteBuffer object having 17 elements. Populates the buffer with two double values. Displays the individual bytes in the buffer. Views the buffer as type double. Views the buffer as type int. Tested using JDK 1.4.0 under Win 2000. The output is: Show empty byte buffer Buffer data for buf1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Populate with double values Show populated buffer as bytes Buffer data for buf1 63 -16 0 0 0 0 0 0 64 0 0 0 0 0 0 0 0 View buffer as type double 1.0 2.0 View buffer as type int 1072693248 0 1073741824 0 **************************************/ import java.nio.*; class ByteBuffer02{ static void showBufferData( ByteBuffer buf, String name){ //Displays byte buffer contents // eight bytes per row. //Save position int pos = buf.position(); //Set position to zero buf.position(0); System.out.println( "Buffer data for " + name); int cnt = 0; while(buf.hasRemaining()){ System.out.print( buf.get() + " "); cnt++; if(cnt%8 == 0)//start new row System.out.println(); }//end while loop System.out.println();//new line //Restore position and return buf.position(pos); }//end showBufferData //---------------------------------// public static void main( String[] args){ ByteBuffer buf1 = ByteBuffer.allocate(17); System.out.println( "Show empty byte buffer"); showBufferData(buf1,"buf1"); System.out.println( "Populate with double values"); buf1.putDouble(1.0); buf1.putDouble(2.0); System.out.println( "Show populated buffer as bytes"); showBufferData(buf1,"buf1"); System.out.println( "View buffer as type double"); buf1.position(0); DoubleBuffer buf2 = //view object buf1.asDoubleBuffer(); while(buf2.hasRemaining()){ System.out.print( buf2.get() + " "); }//end while loop System.out.println();//blank line System.out.println( "View buffer as type int"); IntBuffer buf3 = //view object buf1.asIntBuffer(); while(buf3.hasRemaining()){ System.out.print( buf3.get() + " "); }//end while loop System.out.println();//blank line }// end main }//end class ByteBuffer02 definition Listing 38 |
Copyright 2002, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-