November 24, 2014
Hot Topics:

The ByteBuffer Class in Java

  • August 20, 2002
  • By Richard G. Baldwin
  • Send Email »
  • More Articles »

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.
The ByteOrder class also provides a static method named nativeOrder, which retrieves the native byte order of the underlying platform. (For example, the machine that I am using to compose this lesson reports that its native byte order is LITTLE_ENDIAN).

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
Operations specific to ByteBuffer

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
In each case, the behavior of the operation is appropriate for the type of data encapsulated in the object.

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.

buf5.order(ByteOrder.LITTLE_ENDIAN);

Absolute get and put methods for other types

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.



Page 2 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Enterprise Development Update

Don't miss an article. Subscribe to our newsletter below.

Sitemap | Contact Us

Rocket Fuel