The ByteBuffer Class in Java

Baldwin explains the ByteBuffer class, which is fundamental to many of the new features in Java version 1.4.0.

Published:  August 20, 2002
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:

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:

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.

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

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:

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:

(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

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:

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:

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 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:

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:

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):

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-