Published:
October 15, 2002
By Richard G. Baldwin
Java Programming Notes # 1788
The recently released JavaTM 2 SDK, Standard Edition Version 1.4 contains a large number of new features.
Among the features which are completely new to version 1.4 is the concept of an IO channel. The previous lesson, entitled FileChannel Objects in Java, Background Information, introduced you to the concept of channels from a read/write IO viewpoint.
Data of type byte
This lesson will walk you through a sample program that uses FileChannel objects in conjunction with ByteBuffer objects to perform read/write file IO for data of the primitive type byte.
Mixed primitive types
In the next lesson, I will show you how to use the new I/O classes to transfer data of type double and data of type short between the computer's memory and a physical file. You will learn how to extend the concept to any primitive data type other than boolean.
In a future lesson, I will show you how to use the new I/O classes to create records consisting of sequences of data values of different primitive types, and how to transfer those records between the computer's memory and a physical file.
Memory-mapped IO
Future lessons will teach you how to do memory-mapped IO using channels.
A channel is not a stream
Note that a channel is not the same as a stream, but a channel may be based on a stream. Streams have been supported by Java since the earliest days of Java. Channels were not introduced until version 1.4.
The java.nio.channels package
Much of the support for channels is provided in the package named java.nio.channels. This package defines more than a dozen new classes, and about seven new interfaces, not counting the new exception classes. (In addition, there are four more new packages that begin with the name java.nio.)
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.
Before getting into the details of FileChannel objects, I need to tell you that much of what you will see in this lesson will involve objects of the ByteBuffer class. The ByteBuffer class extends the Buffer class, and as such inherits a number of methods from the Buffer class.
It will be much easier for you to understand this discussion of FileChannel objects if you are already familiar with the features of Buffer and ByteBuffer. I have previously published lessons on these topics entitled Understanding the Buffer class in Java and The ByteBuffer Class in Java. You may find it useful to review those two lessons in preparation for your study of this lesson.
What is a FileChannel?
Sun describes an object of the FileChannel class simply as "A channel for reading, writing, mapping, and manipulating a file." In this lesson, I will show you how to use FileChannel objects for reading files and writing files. In a future lesson, I will show you how to use FileChannel objects for mapping files into memory.
Enough talk, let's see some code!
I will illustrate the FileChannel class using the sample program named Channel01. You will find a complete listing of the program in Listing 16 near the end of the lesson. As is my normal approach, I will discuss this program in fragments.
This program, which was tested using Java SDK version 1.4.0 under Win2000, first writes, and then reads a file using FileChannel objects. The data written to and read from the file is of the primitive type byte.
Import directives
The first fragment, shown in Listing 1, shows the beginning of the class named Channel01. The primary purpose of showing this listing at this point is to emphasize the use of the new java.nio packages, as illustrated by the import directives.
import java.io.*; import java.nio.channels.*; import java.nio.*; class Channel01{ Listing 1 |
The showBufferData method
Listing 2 shows the signature of a static convenience method of my own design named showBufferData. As you might guess from the name and the incoming parameter type, the purpose of this method is to display the contents of a ByteBuffer object. This method will be used later in the program, so you need to understand how it behaves.
static void showBufferData( ByteBuffer buf, String name){ Listing 2 |
A ByteBuffer parameter
As you can see, this method receives a reference to a ByteBuffer object as an incoming parameter. The method displays the byte data encapsulated by that object.
A String parameter
The method also receives a reference to a String object as an incoming parameter. This string is included in the display to make it easier to associate the output with the program code.
Saving the position property
When the ByteBuffer object is passed to the method, its position property may or may not be zero. This method needs to be careful to avoid modifying the value of the position property. Therefore, the method saves the value of the position property upon entry and restores that value upon exit.
int pos = buf.position(); buf.position(0); System.out.println( "Data for " + name); Listing 3 |
The code in Listing 3 begins by invoking one of the overloaded versions of the position method to get and save the value of the position property.
Set position property to zero
Having saved the value of the position property, the code in Listing 3 invokes the other overloaded version of the position method to set the value of the position property to zero.
(Those of you who are familiar with JavaBeans design patterns will recognize that these overloaded versions of the position property do not adhere to the design pattern for property getter and setter methods.)
Display a header
Finally, the code in Listing 3 displays a header for the data that will follow, and identifies that data by including the value of the incoming string named name.
Iterating on a ByteBuffer object
The ByteBuffer class provides methods named hasRemaining and get, which make it easy to iterate on the buffer. The code in Listing 4 uses these two methods in a while loop to read and display the byte at the buffer's current position, and then to automatically increment the position.
while(buf.hasRemaining()){ System.out.print( buf.get() + " "); }//end while loop Listing 4 |
The hasRemaining method
The hasRemaining method in the conditional clause of the while loop returns a boolean, which tells whether there are any elements between the current position and the limit. When the limit is reached, the hasRemaining method returns false, and control exits the while loop.
The relative get method
The get method used in Listing 4 gets and returns the byte at the current position and automatically increments the value of the position property.
Restoring the value of the position property
The code in Listing 5 displays a blank line to separate the blocks of output data. Then it restores the value of the position property, and returns, ending the showBufferData method.
System.out.println();//new line buf.position(pos); }//end showBufferData Listing 5 |
The clearBufferData method
Listing 6 shows the signature for a static convenience method named clearBufferData whose purpose is to store a zero value in each element of a ByteBuffer object received as an incoming parameter.
static void clearBufferData( ByteBuffer buf, String name){ Listing 6 |
The get and put methods of a ByteBuffer
In addition to the get method discussed above, the ByteBuffer class also provides a method named put, which writes a given byte value into the buffer at the current position, and then increments the position.
Very similar to previous code
If you understood the behavior of the showBufferData method discussed above, you should have no difficulty understanding the behavior of the clearBufferData method. The body of that method is shown in its entirety in Listing 7.
//Set position to zero buf.position(0); System.out.println( "Clear " + name); while(buf.hasRemaining()){ buf.put((byte)0); }//end while loop //Set position to zero and return buf.position(0); }//end method Listing 7 |
Now let's talk about channels!
That covers the preliminaries. We now have a convenient and easy way to clear and/or display the contents of a ByteBuffer object, so we can get down to the main topic of this lesson - channels.
For simplicity, the remaining code in this program is contained in the main method, which begins in Listing 8.
Create some raw data
After printing a text header on the screen, the main method in Listing 8 creates and populates a six-element byte[] array object. This will be the raw data for the operations that follow.
public static void main( String[] args){ System.out.println( "Demo write/read on channel"); byte[] array = {65,66,67,68,69,70}; Listing 8 |
Hopefully the code in Listing 8 will be familiar to you. If not, you may need to review some of my introductory Java tutorials, such as the one entitled The Essence of OOP using Java, Array Objects, Part 1. That tutorial is followed by parts 2 and 3.
Creating a ByteBuffer object
The code in Listing 9 wraps the array object in a ByteBuffer object. This code illustrates one of the ways to create a ByteBuffer object in Java.
(If this code is unfamiliar to you, you should probably review my tutorial lessons entitled Understanding the Buffer class in Java and The ByteBuffer Class in Java.)
ByteBuffer buf = ByteBuffer.wrap(array); showBufferData(buf,"buf-raw data"); Listing 9 |
Display contents of ByteBuffer object
Having created a ByteBuffer object that wraps the array object, the code in Listing 9 displays the contents of the ByteBuffer object by invoking the showBufferData method discussed earlier. The output produced by the code in Listing 9 is shown in Figure 1.
Data for buf-raw data 65 66 67 68 69 70 Figure 1 |
As you would expect, the values shown in Figure 1 match the values stored in the array object in Listing 8, which was wrapped in the ByteBuffer object in Listing 9.
Now for the FileChannel class
We've finally reached the point where we are prepared to discuss the FileChannel class. The code in Listing 10 begins by instantiating a new object of the class FileOutputStream, and linking that object to a physical file named junk.txt.
try{ FileChannel outCh = (new FileOutputStream( "junk.txt")).getChannel(); Listing 10 |
Get a FileChannel object for output
Then the code in Listing 10 invokes the getChannel method on the FileOutputStream object's reference to create a FileChannel object linked to the same physical file as the FileOutputStream.
(This FileChannel object can be used to write data to the physical file. However, it cannot be used to read data from the file because it is derived from an output stream.)
The reference to the FileChannel object is stored in a reference variable of type FileChannel named outCh.
Write the buffer data to the physical file
The code in Listing 11 causes the data in the ByteBuffer object to be written into the physical file by invoking the write method of the FileChannel class, passing the buffer object's reference as a parameter.
System.out.println( "Bytes written = " + outCh.write(buf)); outCh.close(); Listing 11 |
The number of bytes written
The write method returns the number of bytes written to the physical file, which is displayed by the code in Listing 11.
The output produced by the code in Listing 11 is shown in Figure 2.
Bytes written = 6 Figure 2 |
As you would expect, the number of bytes written to the physical file matches the number of bytes originally stored in the array object and later wrapped in the ByteBuffer object.
Close the channel
Then the code in Listing 11 invokes the close method to close the FileChannel object.
Clear the buffer
The code in Listing 12 invokes the clearBufferData method discussed earlier to store a zero value in each of the elements of the ByteBuffer object.
clearBufferData(buf,"buf"); Listing 12 |
Note that when the clearBufferData method returns, the position property of the buffer has been set to zero.
Invocation of the clearBufferData method produces the output shown in Figure 3.
Clear buf Figure 3 |
Get a FileChannel object for input
The code in Listing 13 uses an instance of the FileInputStream class as an intermediary to get a FileChannel object that is suitable for reading data from the physical file named junk.txt.
FileChannel inCh = (new FileInputStream( "junk.txt")).getChannel(); Listing 13 |
(Note that this object cannot be used for output because it was derived from an input stream.)
The FileChannel object's reference is stored in the reference variable named inCh.
Read data from physical file into the buffer
The code in Listing 14 invokes the read method on the FileChannel object's reference to read the data from the physical file into the ByteBuffer object, whose reference is passed as a parameter to the read method.
(Recall that the elements of the ByteBuffer object were previously cleared to all zero values.)
System.out.println( "Bytes read = " + inCh.read(buf)); inCh.close(); Listing 14 |
The read method returns the number of bytes actually read, producing the output shown in Figure 4.
Bytes read = 6 Figure 4 |
As you would probably expect, the number of bytes read matches the number of bytes in the physical file, which matches the number of elements in the original array object.
Recap
To recap, the ByteBuffer object was originally created and populated by wrapping an array object of type byte[] having a length of six elements. An output FileChannel object was used to cause the contents of the ByteBuffer object to be written into a physical file named junk.txt.
Then each of the elements in the ByteBuffer object was set to a value of zero, and the position property for the ByteBuffer object was set to zero.
Following this, an input FileChannel object was used to cause the contents of the physical file named junk.txt to be read into the ByteBuffer object.
Display data in the ByteBuffer object
The code in Listing 15 causes the new contents of the ByteBuffer object to be displayed on the computer screen.
showBufferData(buf,"buf"); Listing 15 |
The output produced by the code in Listing 15 is shown in Figure 5.
Data for buf 65 66 67 68 69 70 Figure 5 |
Data matches the original array object
Since nothing was done to modify any of the data anywhere along the way, the values of the elements in the ByteBuffer object match the values of the elements in the original array object, which was the starting point for this series of operations.
What have you learned?
You have learned how to use the write and read methods of the FileChannel class to cause data of type byte to be transferred between a ByteBuffer object and a physical file. You have seen a simple example of why Sun says:
"A channel represents an open connection to an entity such as a hardware device, a file, a network socket, or a program component that is capable of performing one or more distinct I/O operations, ..."
So what!
By now, you may be saying "So what! There are other, possibly easier ways to transfer data of type byte between the computer's memory and a physical file."
Good point
While this sample program illustrates the basics of using channels, it doesn't do a very good job of showing off the advantages of the new channel capability in Java. However, you need to understand the basics before moving on to more complex topics.
The sample program that I will discuss in the next lesson will do a little better in terms of showing off the advantages of channels.
In the next lesson, I will show you how to use the FileChannel class, the ByteBuffer class, the DoubleBuffer class, and the ShortBuffer class to transfer data of type double and data of type short between the computer's memory and a physical file. You will learn how to extend the concept to any primitive data type other than boolean.
In a future lesson, I will show you how to use the FileChannel class along with the ByteBuffer class to create records consisting of sequences of data values of different primitive types, and how to transfer those records between the computer's memory and a physical file.
The examples in the next lesson will better illustrate some of the advantages of channels.
Remember, however, that you must be running Java version 1.4.0 or later to compile and execute this program.
Sun describes an object of the FileChannel class simply as "A channel for reading, writing, mapping, and manipulating a file."
FileChannel objects are tied directly to objects of the ByteBuffer class.
One way to create a ByteBuffer object is to wrap an existing array object in a ByteBuffer object.
You can get a FileChannel object for output by invoking the getChannel method on a FileOutputStream object's reference. The FileChannel object can be used to write data to a physical file. However, it cannot be used to read data from the file.
The write method of a FileChannel object actually transfers data from a ByteBuffer object to a physical file.
You can get a FileChannel object for input by invoking the getChannel method on a FileInputStream object's reference. The FileChannel object can be used to read data from a physical file. However, it cannot be used to write data to the file.
The read method of a FileChannel object actually transfers data into a ByteBuffer object from a physical file.
In the next lesson, I will show you how to use the new I/O classes to transfer data of type double and data of type short between the computer's memory and a physical file. You will learn how to extend the concept to any primitive data type other than boolean.
In a future lesson, I will show you how to use the new I/O classes to create records consisting of sequences of data values of different primitive types, and how to transfer those records between the computer's memory and a physical file.
As time goes on, I plan to publish additional lessons that will help you learn to use other new IO features including:
/* File Channel01.java Copyright 2002, R.G.Baldwin Illustrates use of FileChannel objects. First writes and then reads a file using FileChannel objects. Tested using JDK 1.4.0 under Win2000 The output is: Demo write/read on channel Data for buf-raw data 65 66 67 68 69 70 Bytes written = 6 Clear buf Bytes read = 6 Data for buf 65 66 67 68 69 70 **************************************/ import java.io.*; import java.nio.channels.*; import java.nio.*; class Channel01{ static void showBufferData( ByteBuffer buf, String name){ //Displays byte buffer contents //Save position int pos = buf.position(); //Set position to zero buf.position(0); System.out.println( "Data for " + name); while(buf.hasRemaining()){ System.out.print( buf.get() + " "); }//end while loop System.out.println();//new line //Restore position and return buf.position(pos); }//end showBufferData //---------------------------------// static void clearBufferData( ByteBuffer buf, String name){ //Stores 0 in each element of a // byte buffer. //Set position to zero buf.position(0); System.out.println( "Clear " + name); while(buf.hasRemaining()){ buf.put((byte)0); }//end while loop //Set position to zero and return buf.position(0); }//end clearBufferData //---------------------------------// public static void main( String[] args){ System.out.println( "Demo write/read on channel"); //Create and populate array byte[] array = {65,66,67,68,69,70}; //Wrap array in a buffer ByteBuffer buf = ByteBuffer.wrap(array); showBufferData(buf,"buf-raw data"); //Get FileChannel for output try{ FileChannel outCh = (new FileOutputStream( "junk.txt")).getChannel(); //Write output data System.out.println( "Bytes written = " + outCh.write(buf)); //Close channel outCh.close(); //Clear buffer clearBufferData(buf,"buf"); //Get FileChannel for input FileChannel inCh = (new FileInputStream( "junk.txt")).getChannel(); //Read and display data System.out.println( "Bytes read = " + inCh.read(buf)); //Close channel and display data inCh.close(); showBufferData(buf,"buf"); }catch(Exception e){ System.out.println(e);} }// end main }//end class Channel01 definition Listing 16 |
Copyright 2002, Richard G. Baldwin. Reproduction in whole or in part in any form or medium without express written permission from Richard Baldwin is prohibited.
Richard has participated in numerous consulting projects, and he frequently provides onsite training at the high-tech companies located in and around Austin, Texas. He is the author of Baldwin's Programming Tutorials, which has gained a worldwide following among experienced and aspiring programmers. He has also published articles in JavaPro magazine.
Richard holds an MSEE degree from Southern Methodist University and has many years of experience in the application of computer technology to real-world problems.
-end-