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:

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.