NIO Channels
In many operating systems and programming languages,
a channel is effectively an identifier that represents an open
file, network connection or other I/O device. In NIO, an instance of
Channel provides methods to read and write to an open file or
connection, as well as some other medium-specific functions. Perhaps
most significantly, there is generally a special relationship between a Channel
and a direct ByteBuffer, allowing
data to be transferred to the buffer from the file or connection more efficiently than
to a Java byte array or non-direct buffer.
Opening a channel
As with streams, the way to open a channel depends on the medium.
FileChannels
The FileChannel flavour of channel transfers data to and from
a file, and also provides some other file-related facilities. An instance of
FileChannel is obtained from the getChannel() method
of either a RandomAccessFile or a FileInputStream or FileOutputStream.
The FileChannel thus obtained has read() and write() methods
which accept a ByteBuffer to receive/provide the data as appropriate.
As mentioned, passing a direct ByteBuffer to
these methods is likely to provide the most efficient means of transferring data
to/from the file. Note also:
- although FileChannel has both read() and write()
methods, which of these will actually work depends on the semantics of the underlying
object used to open the file (so e.g. attempting to write() to a channel
opened via a FileInputStream will throw an exception);
- FileChannel provides a position() method for reading/writing
the current position in the file at which reading/writing will occur, and slightly
paradoxically, these methods will work on a channel opened via a stream;
- the channel and underlying stream/RandomAccessFile are linked,
meaning that:
- changing the read/write position via the channel changes the next read/write
position of methods on the stream/RandomAccessFile (and vice versa);
- closing the channel closes the stream/RandomAccessFile (and vice versa).
Converting a Channel to a stream (and vice versa)
A ByteBuffer is sometimes a convenient way to work with data in memory,
even if when it comes to writing that data to a file or other location, a boring old stream would
do the trick. In this case, we simply want the semantics of a Channel— i.e.
the ability to pass a ByteBuffer to its read()/write() methods.
The Channels utility class provides various static methods for wrapping a
Channel around a stream and vice versa. For example, you can call newChannel()
around an InputStream or OutputStream and get an appropriate
Channel to read/write to the given stream. In some cases at least, the
methods on Channels will actually return an appropriate flavour of Channel
that can efficiently access the medium in question (e.g. passing in a
FileInputStream will return the selfsame FileChannel that would have
been obtained by calling getChannel() on the FileInputStrean).
On the other hand, the Channels methods are far from infallible in
this respect: for example, pass in a BufferedInputStream wrapped around a
FileInputStream, and you won't get back a channel that efficiently
reads from the file in the same way that the FileChannel would have.
(In some sense, this is arguably the correct behaviour, but it's worth bearing
in mind.)
Channel-to-channel transfer
Channels generally provide transferTo() and transferFrom() methods
which take another channel as a parameter and allow transfer of data from one channel
to another via the most efficient method available. Potentially, this means that
data can be transferred, say, from one file to another without any intermediate copying
of the data being made in memory.
(Strictly, which of these methods is/are available, and which will function depends
on the particular flavour of channel and whether the underlying medium was opened in
read or write mode.)
The main caveat when using the channel transfer methods is that there appears to be
a not terribly well defined upper limit on the number of bytes that can be transferred in
one go. And in any case, you should generally check the return value of transferTo()
and transferFrom() to see how many bytes were actually transferred
and act accordingly.
If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.
Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.