Java Programming Notes # 1788
Preface
New features in SDK Version 1.4.0
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.
Discussion
and Sample Code
The Buffer and ByteBuffer classes
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.
Run the Program
If you haven’t already done so, I encourage you to copy the code from Listing
16 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.
Summary
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.
What’s Next?
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 locks
- Character-set encoders and decoders
- Pattern matching on files using regular expressions
- Socket channels for both clients and servers
- Non-blocking reads
- Non-blocking servers
Complete Program Listing
A complete listing of the program discussed in this lesson is shown in
Listing 16 below.
/* 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.
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.
-end-