Published: October 1, 2002
By Richard G. Baldwin
Java Programming Notes # 1786
The recently released JavaTM 2 SDK, Standard Edition Version 1.4 contains a number of new features.
Among the features which are new to version 1.4 is the concept of an IO channel. Here is part of what Sun has to say about a channel:
"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, ..."A channel is not the same as 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.) Among the more interesting new classes in the java.nio.channels package are the following:
There is much to learn
As you can readily see from the above list, there is much more to learn about channels than can reasonably be included in a single lesson.
This article is the first in a miniseries that will deal mainly with the FileChannel class. The purpose of this lesson is to introduce you to the concept of channels (and other associated new IO features) from a read/write IO viewpoint.
Future lessons will walk you through sample programs that use FileChannel objects to perform read/write file IO for different primitive data types as well as for data records containing mixed primitive data types.
Future lessons will also provide background material for the use of channels from a memory mapping viewpoint, and will walk you through one or more sample programs that perform memory mapped IO using channels.
Future plans
As time goes on, I plan to publish additional lessons that will help you learn how to use other new IO features including:
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 these lessons will involve objects of classes with names like ByteBuffer, DoubleBuffer, ShortBuffer, etc. These classes extend the Buffer class, and as such inherit a number of methods from the Buffer class. In addition, the subclasses of Buffer provide methods that are appropriate for objects instantiated from the subclasses.
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 these 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 miniseries, I will show you how to use FileChannel objects for reading files and writing files. I will also show you how to map files into memory.
Tied directly to ByteBuffer
FileChannel objects are tied directly to ByteBuffer objects. As mentioned earlier, the ByteBuffer class extends the Buffer class. Hence, a ByteBuffer object is a Buffer object.
A Buffer object is a "container for data of a specific primitive type."
Essential properties
According to Sun, "Aside from its content, the essential properties of a buffer are its capacity, limit, and position." I discuss those three properties in some detail in the lesson on the Buffer class that I referred to earlier, and won't repeat that discussion here. However, for convenience, I will provide Sun's definition of the position property
"A buffer's position is the index of the next element to be read or written."The sample programs in this miniseries make extensive use of the value of the position property of a buffer.
FileChannel also has a position
Similarly, a FileChannel object also has a current position within its file. The current position of a FileChannel object can be both queried and modified.
Getting the FileChannel position
The current position of a FileChannel object can be queried by invoking the overloaded position() method on the FileChannel object's reference. This method returns the channel's file position. This is a non-negative long integer counting the number of bytes from the beginning of the file to the current position.
Setting the FileChannel position
Similarly, the current position of a FileChannel object can be modified by invoking the overloaded position method on the FileChannel object's reference. This overloaded version of the method requires an incoming integer parameter of type long and sets a new position for the channel.
Be careful when setting the position
Care must be exercised when setting a new position for the channel. For example, here is what Sun says will happen if the new position is greater than the file's current size:
"Setting the position to a value that is greater than the file's current size is legal but does not change the size of the file. A later attempt to read bytes at such a position will immediately return an end-of-file indication. A later attempt to write bytes at such a position will cause the file to be grown to accommodate the new bytes; the values of any bytes between the previous end-of-file and the newly-written bytes are unspecified."The physical file
The physical file associated with the FileChannel object contains a variable-length sequence of bytes. These bytes can be read and written. In addition, the current size of the file can be obtained by invoking the size method on the FileChannel object's reference. This method returns the current size of the physical file associated with the channel, measured in bytes as a long integer.
Increasing and reducing the file size
The size of the physical file increases when bytes are written beyond its current size. It is also possible to truncate the file by invoking the truncate method on the FileChannel object's reference, passing a long integer as a parameter to specify the new size.
If the new size is less than the file's current size then the file is truncated. In this case, any bytes beyond the new end of the file are discarded. If the new size is greater than or equal to the file's current size then the file is not modified. If the current position is greater than the new size, then the position is set to the new size.
The ByteChannel interface
The FileChannel class implements several interfaces. One of those interfaces is named ByteChannel. The ByteChannel interface inherits the following method declarations from the interfaces that it extends:
This is where we see the first evidence of the connection between FileChannel objects and ByteBuffer objects. As you can guess from the names, the first two methods in the above list are used to read and write files. As you can see from the signature, these two methods require a reference to a ByteBuffer object as a parameter.
Expected behavior
Any class that implements an interface must provide a concrete definition for all the interface methods. In addition, those classes that implement the ByteChannel interface (including DatagramChannel, FileChannel, and SocketChannel) are expected to provide the following behavior for the read method shown in the above list.
The read method
The read method reads a sequence of bytes from the channel into the ByteBuffer object whose reference is passed as a parameter to the method. In other words, the ByteBuffer passed as a parameter to the method is the destination for the data to be read from the channel.
(Thus, you might think of a channel object as a conduit through which data flows between a file and a ByteBuffer object.)
How much data gets read?
When the read method is invoked, an attempt will be made to read up to r bytes from the channel, where r is the number of bytes remaining in the buffer when the method is invoked. This value can be determined by invoking the remaining method on a reference to the buffer. (For more information on this, see the article referred to earlier that discusses the capacity, limit, and position properties of a buffer.)
(Note that the above description refers to the number of bytes remaining in the buffer, and not the number of bytes remaining in the channel.)
What is remaining?
Briefly, the remaining method returns the number of elements between the current value of the position property (of the buffer) and the current value of the limit property (of the buffer). As a result, when working with ByteBuffer objects, you must always be cognizant of the current position.
(For example, if you attempt to read bytes into a buffer whose position is equal to its limit, no data will be read into the buffer. This type of problem can be very difficult to identify when your program doesn't behave as you might have expected.)How does the read method affect the buffer properties?
Sun provides the following wording to explain the behavior of the read method as it relates to the position in the buffer, the limit of the buffer, the number of bytes remaining in the buffer, and the number of bytes to be read. I can't improve on this wording, so I will simply quote from the Sun documentation. In this description, r is the number of bytes remaining, n is the number of bytes read, and p is the current position
"Suppose that a byte sequence of length n is read, where 0<=n<=r. This byte sequence will be transferred into the buffer so that the first byte in the sequence is at index p and the last byte is at index p+n-1, where p is the buffer's position at the moment this method is invoked. Upon return the buffer's position will be equal to p+n; its limit will not have changed."How many bytes will be read?
I indicated earlier that when the read method is invoked, an attempt is made to read up to r bytes from the channel. Such a read operation may not fill the buffer. In fact it might not read any bytes at all. This will depend on the state of the buffer as well as the nature and state of the channel.
(For example, if position equals limit for the buffer, no data will actually be transferred. Also, a file channel cannot read any more bytes than remain in the file.)Blocking and non-blocking mode
Channels can be operated in a blocking or a non-blocking mode. This is a topic that I plan to discuss in detail in a future lesson. If a channel is in blocking mode and there is at least one byte remaining in the buffer, the read method will block until at least one byte is read.
Thread safety
The read method may be invoked at any time, and is thread-safe in the following sense. If another thread has already initiated a read operation on this channel, an invocation of the read method will block until the first operation is complete.
Return value from the read method
The read method returns the number of bytes actually read (which may be zero). You will see the use of this return value in the sample programs in the remaining lessons in this miniseries.
The method returns -1 if the channel has reached end-of-stream, which for a file channel would be the end of the file.
Exceptions
The read method throws several different types of exceptions, which I won't discuss in detail here. I will refer you to the Sun documentation for a discussion of those exceptions.
The write method
Any class that implements the ByteChannel interface (including FileChannel) is expected to provide the following behavior for the write method shown in the earlier list. The required signature of the write method is repeated below for convenience:
write(ByteBuffer src)A data source
In this case, you will note that the ByteBuffer object, (whose reference is passed as a parameter to the write method), provides the source of the data to be written to the channel. Thus, invocation of the write method writes a sequence of bytes to the channel from the specified buffer.
How many bytes are written?
As with the read method discussed earlier, when the write method is invoked on a FileChannel object, an attempt is made to write up to r bytes to the channel, where r is the number of bytes remaining in the buffer. As mentioned earlier, the number of remaining bytes can be determined by invoking the remaining method on the reference to the ByteBuffer object.
How does the write method affect the properties?
Again, here is how Sun explains the details of invoking the write method with respect to the various properties identified earlier.
"Suppose that a byte sequence of length n is written, where 0<=n<=r. This byte sequence will be transferred from the buffer starting at index p, where p is the buffer's position at the moment this method is invoked; the index of the last byte written will be p+n-1. Upon return the buffer's position will be equal to p+n; its limit will not have changed."When does the write method return?
According to Sun,
"Unless otherwise specified, a write operation will return only after writing all of the r requested bytes."So far, I haven't found any of the standard classes that implement this interface for which the documentation states that the write method will return early (with the possible exception of a socket channel in non-blocking mode, to be discussed in a future lesson).
Thread safety
As with the read method discussed earlier, the write method may be invoked at any time, and is thread-safe in the following sense. If another thread has already initiated a write operation on this channel, an invocation of the write method will block until the first operation is complete.
Return value and exceptions
The write method returns the number of bytes written, which may be zero. It also throws several exceptions. I will refer you to the Sun documentation for a description of those exceptions.
The close method
Any class that implements the ByteChannel interface (including FileChannel) is expected to provide the following behavior for the close method shown in the earlier list.
The close method takes no parameters and returns void. Invocation of the method on a channel closes the channel.
What does it mean to close the channel?
After the close method has been invoked on a channel, any further attempt to invoke IO operations on the channel will cause a ClosedChannelException to be thrown. Invoking the close method on a channel that is already closed has no effect.
Thread safety
As with the read and write methods discussed earlier, the close method may be invoked at any time. However, if some other thread has already invoked the close method on the channel, the second invocation will block until the first invocation is complete. When the first invocation is complete, the second invocation will return having no effect on the channel.
File-specific operations
In addition to the read, write, and close methods applicable to all classes that implement the ByteChannel interface, the FileChannel class defines several file-specific operations.
According to Sun, "File channels are safe for use by multiple concurrent threads." Sun goes on to describe the behavior when different threads attempt to concurrently access the same file for the same or different purposes. I will refer you to the Sun documentation for additional information on this topic, if the topic is of interest to you.
Other interfaces
In addition to the ByteChannel interface, the FileChannel class implements the ScatteringByteChannel interface and the GatheringByteChannel interface (plus several other interfaces as well).
Overloaded read and write methods
The FileChannel class provides four overloaded versions of the read method and four overloaded versions of the write method.
In both cases, only one version of the overloaded methods is declared in the ByteChannel interface. Thus, the FileChannel class provides file-specific read and write capability over and above that declared in the ByteChannel interface.
Two of the four overloaded read methods are declared in the ScatteringByteChannel interface, and two of the overloaded write methods are declared in the GatheringByteChannel interface.
Reading and writing multiple buffers
The signatures of the overloaded versions of the read method that are declared in the ScatteringByteChannel interface are:
Similarly, two overloaded write methods are provided to support gathering write operations. A gathering write operation writes, in a single invocation, a sequence of bytes from one or more of a given sequence of buffers.
Getting a FileChannel object
According to Sun,
"This class does not define methods for opening existing files or for creating new ones; such methods may be added in a future release."
You can get a FileChannel object by invoking the getChannel method on an existing FileInputStream, FileOutputStream, or RandomAccessFile object. This will return a reference to a FileChannel object that is connected to the same underlying file.
The state of a FileChannel object
The state of a FileChannel object is connected to that of the object whose getChannel method returned the reference to the FileChannel object.
Normally, changing the channel's position, whether explicitly or by reading or writing bytes, will change the file position of the originating object, and vice versa. (An exception to this rule will be discussed shortly.)
Similarly, changing the file's length via the file channel will change the length seen via the originating object, and vice versa. Changing the file's content by writing bytes will change the content seen by the originating object, and vice versa.
The fourth overloaded read method
The fourth overloaded read method of the FileChannel class has the following signature:
read(ByteBuffer dst, long position)This method reads a sequence of bytes from the channel into the given buffer, starting at the specified file position. (This is the exception to the norm mentioned above.)
This method works the same as the read(ByteBuffer) method, except that bytes are read starting at the specified file position rather than at the channel's current position. Furthermore, this method does not modify the channel's position. If the specified position is greater than the file's current size then no bytes are read.
A fourth overloaded write method
A similar overloaded write method having the signature shown below works the same as the write(ByteBuffer) method. Again, however, there is an exception to the normal operation described earlier. In this case, bytes are written starting at the specified file position rather than at the channel's current position.
write(ByteBuffer src, long position)This method does not modify the channel's position. If the specified position is greater than the file's current size then the file will be extended to accommodate the new bytes. Note, however, that the values of any bytes between the previous end-of-file and the newly-written bytes are unspecified.
Open for reading and writing
As I mentioned earlier, you can get a FileChannel object that is connected to an underlying file by invoking the getChannel method on an existing FileInputStream, FileOutputStream, or RandomAccessFile object.
A FileChannel object obtained from a FileInputStream object will be open for reading, but not for writing. A FileChannel object obtained from a FileOutputStream object will be open for writing, but not for reading.
Finally, a FileChannel object obtained from a RandomAccessFile object will be open for reading if the object was created in the "r" mode, and will be open for reading and writing if the object was created in the "rw" mode.
Append mode
If you create an instance of a FileOutputStream object using the following constructor (passing true as a parameter), the file will be opened in append mode.
FileOutputStream(File,boolean)
If you get a file channel from that object, the file channel will also be opened in append mode.
According to Sun,
"In this mode each invocation of a relative write operation first advances the position to the end of the file and then writes the requested data."
Also according to Sun,
"Whether the advancement of the position and the writing of the data are done in a single atomic operation is system-dependent and therefore unspecified."
A FileChannel object is a channel for reading, writing, mapping, and manipulating a file.
FileChannel objects are tied directly to ByteBuffer objects. Therefore, you must understand the use of the Buffer class and its subclasses in order to understand the use of the FileChannel class.
A Buffer object is a container for data of a specific primitive type.
The essential properties of a buffer are its capacity, limit, and position.
A buffer's position is the index of the next element to be read or written.
A FileChannel object also has a current position within its file, which can be both queried and modified.
Care must be exercised when setting a new position for a FileChannel object.
The physical file associated with the FileChannel object contains a variable-length sequence of bytes, which may be read and written.
The size of the physical file can be increased or reduced through truncation.
The FileChannel class implements several interfaces, including the ByteChannel interface.
The ByteChannel interface requires that the FileChannel class implement the following methods:
Any class that implements an interface must provide a concrete definition for all the interface methods. In addition, classes that implement the ByteChannel interface are expected to provide certain specific behavior for the read, write, and close methods.
Channels can be operated in a blocking or a non-blocking mode.
In addition to the read, write, and close methods applicable to all classes that implement the ByteChannel interface, the FileChannel class defines several file-specific operations.
According to Sun, "File channels are safe for use by multiple concurrent threads."
In addition to the ByteChannel interface, the FileChannel class implements the ScatteringByteChannel interface and the GatheringByteChannel interface (plus several other interfaces as well).
The FileChannel class provides four overloaded versions of the read method, only one of which is declared in the ByteChannel interface.
Likewise, the FileChannel class provides four overloaded versions of the write method, only one of which is declared in the ByteChannel interface.
Thus, the FileChannel class provides file-specific read and write capability over and above that declared in the ByteChannel interface.
Two of the four overloaded read methods are declared in the ScatteringByteChannel interface. Two of the overloaded write methods are declared in the GatheringByteChannel interface.
A scattering read operation reads, in a single invocation, a sequence of bytes into one or more of a given sequence of buffers.
A gathering write operation writes, in a single invocation, a sequence of bytes from one or more of a given sequence of buffers.
The fourth overloaded read method of the FileChannel class works the same as the read(ByteBuffer) method, except that bytes are read starting at a specified file position.
The fourth overloaded write method of the FileChannel class works the same as the write(ByteBuffer) method except that bytes are written starting at a specified file position.
The FileChannel class does not define methods for opening existing files or for creating new ones.
You can get a FileChannel object by invoking the getChannel method on an existing file IO stream object.
A FileChannel object can be opened for reading, for writing, or for both reading and writing.
A FileChannel object can also be opened for writing in append mode.
Future lessons in this miniseries will walk you through sample programs that use FileChannel objects to perform read/write file IO for different primitive data types as well as for data records containing mixed primitive data types.
For data of type byte, you will see the connection that exists between the physical file, the FileChannel object, and a ByteBuffer object.
For data of type double, you will see the connection that exists between the physical file, the FileChannel object, a ByteBuffer object, and a DoubleBuffer object that provides a double view of data stored in the ByteBuffer object.
For data of type short, you will see the connection that exists between the physical file, the FileChannel object, a ByteBuffer object, and a ShortBuffer object that provides a short view of data stored in the ByteBuffer object.
You will learn that view objects such as DoubleBuffer and ShortBuffer are available for all the primitive types other than boolean.
Thus, in addition to learning about FileChannel objects, you will also learn about some additional interesting aspects of the subclasses of the Buffer class.
Future lessons will also provide background material for the use of channels from a memory mapping viewpoint, and will walk you through one or more sample programs that perform memory mapped IO using channels.
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-