JavaThe ByteBuffer Class in Java

The ByteBuffer Class in Java

Developer.com content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More.






… in Java by Richard G Baldwin

Java Programming Notes # 1782


Preface


New features in SDK Version 1.4.0

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:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

I explained the use of the Buffer class in a previous article entitled
Understanding
the Buffer class in Java
. As shown above, one of the subclasses of
Buffer
is named ByteBuffer.  The ByteBuffer class is very important
because it forms a basis for the use of channels in Java. (The concept
of I/O channels is another new feature of version 1.4, which I plan to
cover in a future lesson.)

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.

Discussion
and Sample Code


The inheritance model

As mentioned above, the class named ByteBuffer extends the abstract
class named Buffer.  The Sun documentation lists the following
known subclasses of Buffer:

  • ByteBuffer
  • CharBuffer
  • DoubleBuffer
  • FloatBuffer
  • IntBuffer
  • LongBuffer
  • ShortBuffer

Four common capabilities

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.

  • Absolute and relative get and put methods that
    read and write single elements.
  • Relative get methods that transfer contiguous sequences of
    elements from the buffer into an array.
  • Relative put methods that transfer contiguous sequences of
    elements from an array (of the same type) or some other buffer
    (of
    the same type)
    into a buffer.
  • Methods for compacting, duplicating, and slicing a buffer.

(In addition, some of the subclasses provide capabilities that are specific
to the type involved.  For example, the CharBuffer class provides
capabilities that are specific to the use of characters and strings, such
as a method that wraps a String into a CharBuffer object.)

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.

  • Absolute and relative get and put methods that read
    and write values of primitive types other than byte.
  • Methods for creating view buffers, which allow a byte buffer to
    be viewed as a buffer containing values of some other primitive type.

In addition, as I mentioned earlier, the ByteBuffer class is very
important in understanding I/O channels in Java, which I intend
to discuss in a future lesson.

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:

  • capacity:  The number of elements the buffer contains.
  • limit:  The index of the first element that should not be read
    or written.
  • position:  The next element to be read or written.

Various methods are provided by the Buffer
class to deal with these properties, and these methods are inherited into
the ByteBuffer class.  I will use some of those methods in
the sample program in this lesson.  However, an earlier lesson (see
the link above)
that explained the use of the Buffer class also
explained how to use all of the property-manipulating methods of the Buffer
class.  If you aren’t familiar with those methods, you might want
to refer back to that lesson.

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:

  • capacity
  • limit
  • position

(Note that the property getter methods of the Buffer class do
not conform to the JavaBeans design patterns for property getter methods.)

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 of the Buffer class
  • The relative get method of the ByteBuffer class

The hasRemaining method

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 values of the elements at positions 3, 4, and 5 in the buffer are changed
    to 20, 21, and 22 respectively.
  • The values of the corresponding elements in the array are also changed
    accordingly.

Absolute get method

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:

  • The array referred to by a2 as the destination array into which
    bytes are to be written
  • A value of 3 for the offset within the array of the first byte to be written
  • A value of 5 for the maximum number of bytes to be written to the given
    array

This version of the get method copies the specified number of bytes
from the buffer into the array, starting at the current position
of the buffer and at the given offset in the array. The position
of the buffer is then incremented by the specified number of bytes.

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 array referred to by a2 as the source array from which the bytes
    are to be read
  • A value of 1 for the offset within the array of the first byte to be read
  • A value of 8 for the maximum number of bytes to be read from the array

The output

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:

  • 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.

Run the Program


If you haven’t already done so, I encourage you to copy the code from Listing
37 into your text editor, compile it, and execute it.  Experiment
with it, making changes, and observing the results of your changes.

Remember, however, that you must be running Java version 1.4.0 or later
to compile and execute this program.

Complete Program Listing


Complete listings of the programs discussed in this lesson are shown in
Listing 37 and 38 below.

 

/* 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.

About the author

Richard Baldwin
is a college professor (at Austin Community College in Austin, TX) and
private consultant whose primary focus is a combination of Java, C#, and
XML. In addition to the many platform and/or language independent benefits
of Java and C# applications, he believes that a combination of Java, C#,
and XML will become the primary driving force in the delivery of structured
information on the Web.

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.

baldwin@DickBaldwin.com

-end-

 

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Latest Posts

Related Stories