Channels are the second major addition to java.nio
after buffers which we have learned in my previous tutorial in detail. Channels provide direct connection to the I/O services.
A Channel is a medium that transports data efficiently between byte buffers and the entity on the other end of the channel (usually a file or socket).
Usually, channels have a one-to-one relationship with operating-system file descriptors. The Channel
classes provide the abstraction needed to maintain platform independence but still model the native I/O capabilities of modern operating systems.
Channels are gateways through which the native I/O services of the operating system can be accessed with a minimum of overhead, and buffers are the internal endpoints used by channels to send and receive data.
1. Java NIO Channel
At the top of hirarchy, there is Channel interface which looks like this:
package java.nio.channels; public interface Channel { public boolean isOpen(); public void close() throws IOException; }
Channel
implementations vary radically between operating systems due to various factors that depend on the underlying platform, so the channel APIs (or interfaces) simply describe what can be done.
Channel
implementations often use native code to perform actual work. In this way, the channel interfaces allow you to gain access to low-level I/O services in a controlled and portable way.
As you can see by the top-level Channel
interface, there are only two operations common to all channels: checking to see if a channel is open (isOpen()
) and closing an open channel (close()
).
1.1. Opening a Channel
As we already know that I/O falls into two broad categories:
- File I/O
- Stream I/O
So it’s no surprise that there are two types of channels: file and socket. FileChannel
class and SocketChannel
classes are used to deal with these two categories.
FileChannel
object can be obtained only by calling the getChannel()
method on an open RandomAccessFile
, FileInputStream
, or FileOutputStream
object. You cannot create a FileChannel
object directly.
Example 1: How to get a FileChannel
RandomAccessFile raf = new RandomAccessFile ("somefile", "r"); FileChannel fc = raf.getChannel();
Opposite to FileChannel
, socket channels have factory methods to create new socket channels directly.
Example 2: How to create a SocketChannel
//How to open SocketChannel SocketChannel sc = SocketChannel.open(); sc.connect(new InetSocketAddress("somehost", someport)); //How to open ServerSocketChannel ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind (new InetSocketAddress (somelocalport)); //How to open DatagramChannel DatagramChannel dc = DatagramChannel.open();
The above methods return a corresponding socket channel object. They are not sources of new channels as RandomAccessFile.getChannel()
is. They return the channel associated with a socket if one already exists; they never create new channels.
1.2. Using the Channels
As we already learned into buffers tutorial that channels transfer data to and from ByteBuffer
objects. Most of the read/write operations are performed by methods implemented from below interfaces.
public interface ReadableByteChannel extends Channel { public int read (ByteBuffer dst) throws IOException; } public interface WritableByteChannel extends Channel { public int write (ByteBuffer src) throws IOException; } public interface ByteChannel extends ReadableByteChannel, WritableByteChannel { }
Channels can be unidirectional or bidirectional.
A given channel class might implement ReadableByteChannel
, which defines the read()
method. Another might implement WritableByteChannel
to provide write()
.
A class implementing one or the other of these interfaces is unidirectional: it can transfer data in only one direction. If a class implements both interfaces (or ByteChannel
which extends both interfaces), it is bidirectional and can transfer data in both directions.
If you go through Channel
classes, you’ll find that each of the file and socket channels implement all three of these interfaces. In terms of class definition, this means that all the file and socket channel objects are bidirectional.
This is not a problem for sockets because they’re always bidirectional, but it is an issue for files. A FileChannel
object obtained from the getChannel()
method of a FileInputStream
object is read-only but is bidirectional in terms of interface declarations because FileChannel
implements ByteChannel
.
Invoking write()
on such a channel will throw the unchecked NonWritableChannelException
because FileInputStream
always opens files with read-only permission. So remember that when a channel connects to a specific I/O service, the capabilities of a channel instance will be constrained by the characteristics of the service to which it’s connected.
A Channel instance connected to a read-only file cannot write, even though the class to which that channel instance belongs may have a write()
method. It falls to the programmer to know how the channel was opened and not to attempt an operation the underlying I/O service won’t allow.
Example 3: We cannot write to a read-only file using any channel
FileInputStream input = new FileInputStream ("readOnlyFile.txt"); FileChannel channel = input.getChannel(); // This will compile but will throw an IOException // because the underlying file is read-only channel.write (buffer);
The read()
and write()
methods of ByteChannel
take ByteBuffer
objects as arguments. Each returns the number of bytes transferred, which can be less than the number of bytes in the buffer, or even zero. The position of the buffer will have been advanced by the same amount.
If a partial transfer was performed, the buffer can be resubmitted to the channel to continue transferring data where it left off. Repeat until the buffer’s hasRemaining()
method returns false.
In the below example, we are copying data from one channel to another channel (or from one file to another file).
Example 4: Copying data from one channel to another channel in Java
import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; public class ChannelCopyExample { public static void main(String args[]) throws IOException { FileInputStream input = new FileInputStream ("testIn.txt"); ReadableByteChannel source = input.getChannel(); FileOutputStream output = new FileOutputStream ("testOut.txt"); WritableByteChannel dest = output.getChannel(); copyData(source, dest); source.close(); dest.close(); } private static void copyData(ReadableByteChannel src, WritableByteChannel dest) throws IOException { ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024); while (src.read(buffer) != -1) { // Prepare the buffer to be drained buffer.flip(); // Make sure that the buffer was fully drained while (buffer.hasRemaining()) { dest.write(buffer); } // Make the buffer empty, ready for filling buffer.clear(); } } }
Channels can operate in blocking or non-blocking modes. A channel in non-blocking mode never puts the invoking thread to sleep. The requested operation either completes immediately or returns a result indicating that nothing was done. Only stream-oriented channels, such as sockets and pipes, can be placed in non-blocking mode.
1.3. Closing a Channel
To close a channel, use it’s close()
method. Unlike buffers, channels cannot be reused after closing it. An open channel represents a specific connection to a specific I/O service and encapsulates the state of that connection. When a channel is closed, that connection is lost, and the channel is no longer connected to anything.
It’s harmless to call
close()
on a channel multiple times. Subsequent calls toclose()
on the closed channel do nothing and return immediately.
The open state of a channel can be tested with the isOpen()
method. If it returns true, the channel can be used. If false, the channel has been closed and can no longer be used.
Attempting to read, write, or perform any other operation that requires the channel to be in an open state will result in a ClosedChannelException
.
Happy Learning !!