Understanding the Buffer class in Java

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

Published:  June 15, 2002
By Richard G. Baldwin

Java Programming Notes # 1780


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.  I plan to publish articles explaining how to use some of those new features from time to time, and this is the first such article.

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.  One of those subclasses is named ByteBuffer.  Since Buffer is abstract, you can only work with it in terms of its subclasses.  In this article, I will use the ByteBuffer class to explore the features of the Buffer class.

You must understand how to use the Buffer class and its subclasses before you can understand how to use many of the other classes in the API.  Therefore, the main purpose of this lesson is to help you understand how to use the features of the Buffer class.  I will describe many of those features, and will illustrate the use of those features by explaining the code in a sample program.

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 Baldwin's Java Programming Tutorials.

Discussion and Sample Code

The inheritance model

The class named ByteBuffer extends the abstract class named Buffer.  Because Buffer is abstract, it is not possible to create an instance of Buffer.  Rather, the capabilities of Buffer become available when you create an instance of one of the subclasses of Buffer.  The Sun documentation lists the following known subclasses of Buffer:

As you can see from the names of the subclasses, there is one subclass of the Buffer class for each non-boolean primitive type.  I will use the ByteBuffer subclass in this lesson to illustrate the features inherited from the Buffer class.

A container for primitive data

You may already be aware that none of the container classes in the Java Collections Framework are designed to contain primitive data.  Rather, those containers are all designed to contain references to objects.  If you want to store primitive data in one of those containers, you must first wrap the primitive value in an object.

Sun describes Buffer as "A container for data of a specific primitive type."  However, as you will see in a subsequent lesson, an object of the ByteBuffer class can also be used as a container for storing a mixture of data of many different primitive types.

Three important properties

Sun tells us " ...the essential properties of a buffer are its capacity, limit, and position."

I will illustrate these three properties in the sample program later in this lesson.  For now, here is a brief description of each of the three properties of a buffer:

put and get operations

Subclasses of Buffer (such as ByteBuffer) use put and get operations to store data into a buffer and to retrieve 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 retrieve 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.  Transfer requests that exceed the limit cause exceptions to be thrown with no data being 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.  Absolute put and get operations throw an exception if the index exceeds the limit.

The channel concept

Here is another important quotation from Sun.  I show it here simply to introduce the concept of a channel, which will be the topic of a future lesson.

"Data may ... be transferred into or out of a buffer by the I/O operations of an appropriate channel, ..."
One of the main reasons for discussing Buffer in this lesson is to prepare you to understand I/O channels, which I plan to discuss in a subsequent lesson

Mark and reset

The Buffer class also supports the concept of mark and reset.  It is just about impossible to discuss one without the other.  For example, according to Sun, here is a description of the behavior of the reset method:

"Resets this buffer's position to the previously-marked position."
Similarly, here is Sun's description of a buffer's mark.
"A buffer's mark is the index to which its position will be reset when the reset method is invoked."
Interaction rules

Here are some rules that apply to the interaction of mark, position, limit, and capacity:

A new buffer always has a position of zero and a mark that is undefined. The initial limit and capacity of a new buffer depend on the type of the buffer and its construction.

Setter and getter methods

The Buffer class provides methods for setting and getting the values of the position, and limit properties, and for getting the value of the capacity property.  However, these methods do not conform to JavaBeans design patterns for properties.  For example, here are descriptions of the methods for getting and setting the limit property of a buffer:

As you can see, unlike JavaBeans design patterns, the difference between setting and getting in this case is based on overloading the method name.  This is typical of the setter and getter methods for each of the three properties listed earlier.

Method chaining

Note that the second method described above returns a reference to the buffer.  Other methods of the Buffer class also return a reference to the buffer.  This makes it possible to use method invocation chaining syntax such as that shown in Figure 1.
 
buf.rewind().position(2).mark();

Figure 1

I will have more to say about this later.

Other useful methods

The Buffer class defines several other methods that can be used to operate on a buffer, including the following.  The behaviors of these methods, (with respect to changing the values of position and limit), are very important.

I will illustrate most of these methods in the sample program later in this lesson.

Read-only buffers

It is possible to create buffers that are readable but not writable.

(For example, see the documentation for the asReadOnlyBuffer method of the ByteBuffer class.)
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 ByteBuffer class

As mentioned earlier, the ByteBuffer class extends the Buffer class, and as such, inherits the capabilities discussed above.  In addition, the ByteBuffer class provides new capabilities that are not defined in the Buffer class.

I will use the ByteBuffer class in a sample program to illustrate the features inherited from the Buffer class.  I encourage you to compile and execute this sample program, and to experiment with it by making changes while observing the results of your changes.

Reading and writing single bytes

The ByteBuffer class inherits numerous features from the Buffer class, and adds new features of its own.  I will discuss the new features added by the ByteBuffer class in a future lesson.  In this lesson, I will concentrate on the features inherited from the Buffer class, and will limit the use of ByteBuffer features to those required to illustrate the inherited features.

The put and get methods

The abstract Buffer class does not provide methods for storing or retrieving data from a buffer.  Rather, that capability is provided by ByteBuffer and other subclasses of Buffer.

The ByteBuffer class provides get and put methods for reading and writing single bytes in both an absolute and a relative sense.  These features, along with others, are illustrated in the program named Buffer01, which I will discuss in fragments.  A complete listing of the program is provided in Listing 17 near the end of the lesson.

The sample program named Buffer01

The sample program named Buffer01 illustrates most of the methods of the Buffer class and some of the methods of the ByteBuffer class.

Listing 1 shows the beginning of the controlling class named Buffer01.  Listing 1 also shows the import directive used by this program, to remind you that this is a new API for input/output.
 
import java.nio.*;

class Buffer01{

Listing 1

The java.nio package did not exist prior to the release of version 1.4.0.  Therefore, in order to compile and execute this sample program, you will need to have version 1.4.0 or later installed on your system.

Displaying buffer properties

Listing 2 shows the first of three convenience methods designed to make the code in the main body of the program simpler and easier to understand.
 
  static void showBufferProperties(
                           Buffer buf){
    System.out.println(
       "Buffer Properties: "
                    +"\n  capacity=" 
                    + buf.capacity()
                    + " limit=" 
                    + buf.limit()
                    + " position=" 
                    + buf.position());
  }//end showBufferProperties

Listing 2

The purpose of the method named showBufferProperties is to get and display the capacity, limit, and position properties of a Buffer object whose reference is received as an incoming parameter.

(The actual type of the objects passed to this method in this program is ByteBuffer.  However, because ByteBuffer extends Buffer, and all the methods invoked by the code in this method are defined in Buffer, the type of the incoming object can be Buffer or any subclass of Buffer.)
The boldface code in Listing 2 identifies the getter methods used to get the values of the three properties listed above.  The remaining code in Listing 2 is a large print statement that causes the values returned by the getter methods to be displayed on the standard output device.  A sample of the screen output produced by this method is shown in Figure 2.
 
Buffer Properties:
  capacity=8 limit=8 position=0

Figure 2

Display buffer data

The method shown in Listing 3 gets and displays the data stored in the buffer beginning with the element at the current value of the position property and extending to the value of the limit property.
 
  static void showBufferData(
                       ByteBuffer buf){
    System.out.println(
                   "Show buffer data");
    while(buf.hasRemaining())
      System.out.print(
                      buf.get() + " ");
    System.out.println();//blank line
  }//end showBufferData

Listing 3

This is accomplished using the relative get method defined in the ByteBuffer class.

Parameter is not type Buffer

Note that the incoming parameter to this method is type ByteBuffer and is not type Buffer, as was the case in Listing 2.  This is because the get method invoked by the code in this method is not defined in the Buffer class.  Rather, it is defined in the ByteBuffer class, and therefore can only be invoked on a reference of type ByteBuffer, or a subclass of the ByteBuffer class.

The hasRemaining method

The code in Listing 3 invokes two interesting methods.  The first is the hasRemaining method.  This method is much like the methods of the Iterator and Enumeration interfaces, used to iterate on objects instantiated from the concrete classes of the Java Collections Framework.

The hasRemaining method is defined in the Buffer class, and tells whether there are any elements remaining between the current position and the limit.  This method returns a boolean, which is true only if there is at least one element remaining in the buffer.  Thus, it works very nicely in the conditional clause of a while loop for the purpose of iterating on a buffer.

The relative get method

The second method of interest in Listing 3 is the relative get method of the ByteBuffer class.  This method 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).

Display array data

The third convenience method, shown in Listing 4, is a 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] + " ");
    }//end for loop
    System.out.println();//blank line
  }//end showArrayData

Listing 4

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 Baldwin's Java Programming Tutorials.

The main method creates an array object

There are several ways to create and populate a buffer in Java.  One of those ways is to wrap an existing array object in a buffer.  To do that, you need an existing array object.  (I will discuss the other ways to create a buffer in a future lesson.)

Listing 5 shows the beginning of the main method.  The code in Listing 5 creates, populates, and displays an eight-element array object containing data of type byte.
 
  public static void main(
                        String[] args){
    System.out.println(
      "Create, populate, and display "
       + "\nan 8-element byte array");
    byte[] array = {0,1,2,3,4,5,6,7};
    showArrayData(array);

Listing 5

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 5 produces the output shown in Figure 3.
 
Create, populate, and display
an 8-element byte array
Show array data
0 1 2 3 4 5 6 7

Figure 3

I show this here because we will want to compare it with the data stored in our buffer object later.

Create the buffer

As mentioned above, there are several ways to create a buffer, and one of them is shown in Listing 6.
 
    System.out.println(
                 "Wrap the byte array "
                      + "in a buffer");
    ByteBuffer buf = 
                ByteBuffer.wrap(array);

Listing 6

Listing 6 invokes the static wrap method of the ByteBuffer class to create a buffer that wraps an existing array object.

What is the significance of wrapping an array object?

There are two overloaded versions of the wrap method, one that requires incoming offset and length parameters, and one that does not.  (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.

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.  Modifications to the array cause the buffer contents to be modified.  (It appears as though they are really the same set of data.)

For the version of the wrap method that I used, the capacity and limit of the new buffer is 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.

Show buffer properties and data

The code in Listing 7 displays the properties of the new buffer.  Then it uses the relative get method to display the contents of the buffer.  After that, it displays the properties again.
 
    showBufferProperties(buf);
    showBufferData(buf);
    showBufferProperties(buf);

Listing 7

The output produced by the code in Listing 7 is shown in Figure 4.
 
Buffer Properties:
 capacity=8 limit=8 position=0
Show buffer data
0 1 2 3 4 5 6 7
Buffer Properties:
 capacity=8 limit=8 position=8

Figure 4

There are several important things to note about this output:

Modifications to the buffer ...

I stated earlier, "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 illustrated in the next several fragments.
 
    System.out.println(
         "Modify first array element");
    array[0] = 10;
    showArrayData(array);

Listing 8

The code in Listing 8 changes the value in the first array element from 0 to 10, and then displays the modified contents of the array object.  We will see that this causes the value of the first element in the buffer to change accordingly.

Flip the buffer

Before we can use the showBufferData method to display the contents of the buffer, we must do something about the position property whose value is currently 8.  There are several ways to do this, but I took this opportunity to illustrate the use of the flip method of the Buffer class.  The use of the flip method is shown in Listing 9.
 
    System.out.println(
                    "Flip the buffer");
    buf.flip();

Listing 9

According to Sun, the flip method "makes a buffer ready for a new sequence of ... relative get operations.  It sets the limit to the current position and then sets the position to zero."

That is exactly what I needed to do in this case, so the flip method worked quite nicely.

Display buffer properties and data

Listing 10 displays the new property values for the buffer, and then displays the contents of the buffer.
 
    showBufferProperties(buf);
    showBufferData(buf);

Listing 10

The output produced by Listings 8, 9, and 10 is shown in Figure 5.
 
Modify first array element
Show array data
10 1 2 3 4 5 6 7
Flip the buffer
Buffer Properties:
 capacity=8 limit=8 position=0
Show buffer data
10 1 2 3 4 5 6 7

Figure 5

The important things to note in Figure 5 are:

Rewind the buffer

A little later, I will illustrate that changing the contents of the buffer causes the corresponding contents of the wrapped array to change accordingly.  First, however, I need to illustrate the rewind operation on the buffer.  This is accomplished in Listing 11.
 
    System.out.println(
                  "Rewind the buffer");
    buf.rewind();
    showBufferProperties(buf);
    showBufferData(buf);

Listing 11

The code in Listing 11 invokes the rewind method on the buffer and then displays the properties and contents of the buffer.

The rewind method is a method of the Buffer class.  According to Sun, the rewind method "makes a buffer ready for re-reading the data that it already contains: It leaves the limit unchanged and sets the position to zero."  You might equate this to rewinding a VCR tape in order to play it again.

And the output is ...

The code in Listing 11 produces the output shown in Figure 6.
 
Rewind the buffer
Buffer Properties:
 capacity=8 limit=8 position=0
Show buffer data
10 1 2 3 4 5 6 7

Figure 6

There are no surprises here.  By now, you probably knew what to expect as output from this operation.

The absolute put method

As I explained earlier, the ByteBuffer class provides both absolute and relative versions of the put and get methods.  So far, we have seen the use of the relative version of the get method only.  The boldface code in Listing 12 uses the absolute version of the put method to modify the contents of the buffer at index 3.  In particular, the value stored at index 3 in the buffer is overwritten by the value 20.
 
    System.out.println(
            "Modify the buffer using");
    System.out.println(
                "absolute put method");
    buf.put(3,(byte)20);
    buf.rewind();
    showBufferData(buf);
    showArrayData(array);

Listing 12

After the value is modified, the buffer is rewound.  Then the data in the buffer and the data in the array are displayed for comparison.  The output produced by Listing 12 is shown in Figure 7.
 
Modify the buffer using
absolute put method
Show buffer data
10 1 2 20 4 5 6 7
Show array data
10 1 2 20 4 5 6 7

Figure 7

Perhaps the most important things to observe in this output are:

Chaining and marking

As explained earlier, several of the methods of the Buffer class return a reference to the buffer.  This makes it possible to chain method invocations as shown by the boldface code in Listing 13.
 
    System.out.println(
     "Mark at index 2 using chaining");

    buf.rewind().position(2).mark();

Listing 13

The boldface statement in Listing 13 is executed from left to right.  The behavior of the statement accomplishes the following operations in order:

Now change the position property value

The code in Listing 14 changes the value of the position property from 2 to 4.  (It is important to note that this does not change the mark.)
 
    System.out.println(
                  "Set position to 4");
    buf.position(4);
    showBufferData(buf);

Listing 14

Having changed the value of the position property, Listing 14 invokes the showBufferData method to display the contents of the buffer.  The screen output is shown in Figure 8.
 
Mark at index 2 using chaining
Set position to 4
Show buffer data
4 5 6 7

Figure 8

Recall that the showBufferData method displays the data from the current position to the limit.  Therefore, in this case, the display does not begin with the element at index zero.  Rather, it begins with the element at index 4, which is the current value of the position property.

Reset to a previous mark

Listing 15 invokes the reset method on the buffer, and then displays its contents again.
 
    System.out.println(
             "Reset to previous mark");
    buf.reset();
    showBufferData(buf);

Listing 15

The reset method is a method of the buffer class.  According to Sun, invocation of the reset method "Resets this buffer's position to the previously-marked position."

Recall that the previously marked position was the element at index value 2.  Thus, when the showBufferData method is used to display the contents of the buffer, the screen output is as shown in Figure 9.
 
Reset to previous mark
Show buffer data
2 20 4 5 6 7

Figure 9

The output shows that the value of the position property is set to element index 2 when the reset method is invoked.  Then the showBufferData method displays the contents of the buffer from index 2 to the limit.

Thus, the ability to mark and reset makes it possible for your program to remember a position and return to that position later.  You need to exercise caution, however, because several of the operations that you can perform on a buffer cause the mark to be discarded.  If you invoke reset on a buffer for which the mark has been discarded, an exception will be thrown.

Is this a read-only buffer?

Although it is possible to create read-only buffers, the output produced by the code in Listing 16 shows that this is not a read-only buffer.
 
    System.out.println(
                "Buffer is read only: "
                   + buf.isReadOnly());
Listing 16

Listing 16 produces the output shown in Figure 10.
 
Buffer is read only: false

Figure 10

So there you have it

By now you should understand a lot about the new Buffer class in the new java.nio package.

Future articles will discuss ByteBuffer, Channels, and other new I/O features introduced in Java version 1.4.0.

Run the Program

If you haven't already done so, I encourage you to copy the code from Listing 17 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 version 1.4.0 or later to compile and execute this program.

Complete Program Listing

A complete listing of the program is shown in Listing 17 below.
 
/* File Buffer01.java
Copyright 2002, R.G.Baldwin

Illustrates most methods of Buffer 
class and some methods of ByteBuffer
class.

Tested using JDK 1.4.0 under Win2000

Output is:

Create, populate, and display
an 8-element byte array
Show array data
0 1 2 3 4 5 6 7

Wrap the byte array in a buffer
Buffer Properties:
  capacity=8 limit=8 position=0
Show buffer data
0 1 2 3 4 5 6 7
Buffer Properties:
  capacity=8 limit=8 position=8

Modify first array element
Show array data
10 1 2 3 4 5 6 7
Flip the buffer
Buffer Properties:
  capacity=8 limit=8 position=0
Show buffer data
10 1 2 3 4 5 6 7

Rewind the buffer
Buffer Properties:
  capacity=8 limit=8 position=0
Show buffer data
10 1 2 3 4 5 6 7

Modify the buffer using
absolute put method
Show buffer data
10 1 2 20 4 5 6 7
Show array data
10 1 2 20 4 5 6 7

Mark at index 2 using chaining
Set position to 4
Show buffer data
4 5 6 7
Reset to previous mark
Show buffer data
2 20 4 5 6 7

Buffer is read only: false
**************************************/

import java.nio.*;

class Buffer01{
  static void showBufferProperties(
                           Buffer buf){
    System.out.println(
       "Buffer Properties: "
                    +"\n  capacity=" 
                    + buf.capacity()
                    + " limit=" 
                    + buf.limit()
                    + " position=" 
                    + buf.position());
  }//end showBufferProperties
  //---------------------------------//
  
  static void showBufferData(
                       ByteBuffer buf){
    //Displays buffer contents from 
    // current position to limit using
    // relative get method.
    System.out.println(
                   "Show buffer data");
    while(buf.hasRemaining())
      System.out.print(
                      buf.get() + " ");
    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] + " ");
    }//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, populate, and display "
       + "\nan 8-element byte array");
    byte[] array = {0,1,2,3,4,5,6,7};
    showArrayData(array);
    System.out.println();//blank line
    
    System.out.println(
                 "Wrap the byte array "
                      + "in a buffer");
    ByteBuffer buf = 
                ByteBuffer.wrap(array);
    showBufferProperties(buf);
    showBufferData(buf);
    showBufferProperties(buf);
    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");
    array[0] = 10;
    showArrayData(array);

    System.out.println(
                    "Flip the buffer");
    buf.flip();
    showBufferProperties(buf);
    showBufferData(buf);
    System.out.println();//blank line
    
    System.out.println(
                  "Rewind the buffer");
    buf.rewind();
    showBufferProperties(buf);
    showBufferData(buf);
    System.out.println();//blank line

    System.out.println(
            "Modify the buffer using");
    System.out.println(
                "absolute put method");
    buf.put(3,(byte)20);
    buf.rewind();
    showBufferData(buf);
    showArrayData(array);
    System.out.println();//blank line
    
    //Illustrate chaining, marking, 
    // and reset
    System.out.println(
     "Mark at index 2 using chaining");
    buf.rewind().position(2).mark();
    System.out.println(
                  "Set position to 4");
    buf.position(4);
    showBufferData(buf);
    System.out.println(
             "Reset to previous mark");
    buf.reset();
    showBufferData(buf);
    System.out.println();//blank line
    
    System.out.println(
                "Buffer is read only: "
                   + buf.isReadOnly());
  }// end main

}//end class Buffer01 definition

Listing 17


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.richard@iname.com

-end-