Converting Between Streams and Channels
Channels are cool, but streams aren't going away. In many cases, especially with small amounts of data, streams are just faster. Other times, they're more convenient. And sometimes they're part of a legacy API. For instance, I've seen a lot of XML libraries for Java. I've even written one or two, but I've yet to encounter one that uses buffers and channels. They all have deep dependencies on streams. Thus, even when using channels, you will find cases where you also need to interact with stream-based I/O.
The java.nio.Channels class provides eight static utility methods for connecting channels to streams and vice versa. It can also connect channels to readers and writers. (We'll get to those in Chapter 20.)
15.3.1. Converting Channels to Streams
The newInputStream( ) method converts any ReadableByteChannel, including FileChannels, into an InputStream:
public static InputStream newInputStream(ReadableByteChannel ch)
You can use the methods of the InputStream class to read from the channel. Most importantly, you can pass the InputStream object to another method that knows how to work with streams but doesn't know what to do with a channel. For example, suppose you discover that your XML processing is I/O bound and you need to speed up the filesystem access. You could try a FileChannel to do that:
XMLReader parser = XMLReaderFactory.createXMLReader( ); FileInputStream in = new FileInputStream("document.xml"); FileChannel channel = in.getChannel( );
Now say you want to pass this channel to the XML parser. However, the parser will accept only an InputStream, not a channel, so instead you do this:
in = Channels.newInputStream(channel); parser.parse(in);
At this point you may be objecting. You started with an input stream. This was then turned into a channel. The channel was then turned back into an input stream. What has really been gained? The difference is that the raw I/O is now done with channels rather than streams. The original FileInputStream in is only used to create the channel. Its read( ) methods are never called. The actual disk reading is done by native file channel code that should be quite fast. Of course, this is all hypothetical. Whether this strategy would really improve performance would have to be carefully measured on the particular systems where you planned to run the code.
Sometimes it's an OutputStream that's needed. In this case, the Channels.newOutputStream( ) method serves to convert a WritableByteChannel into an OutputStream:
public static OutputStream newOutputStream(WritableByteChannel ch)
One advantage of these streams is that, unlike most streams, they are threadsafe. That is, these streams can be shared between multiple threads, and Java will ensure that the reads and writes are atomic and do not interrupt each other. This alone may be sufficient reason to use these methods instead of just creating the streams directly.
15.3.2. Converting Streams to Channels
Sometimes you need to go the other direction, taking an existing stream and changing it into a channel. This isn't necessary for file channels or network channels, which have their own special channel classes. However, it may be necessary to use this approach to get channels from more obscure streams, such as a GZipInputStream or a ProgressMonitorInputStream, or you may have a class such as the Apache HTTPClient's InputStreamRequestEntity that gives you a stream that you want to read or write using new I/O. There are two newChannel( ) methods, depending on whether you want a WritableByteChannel for output or a ReadableByteChannel for input:
public static ReadableByteChannel newChannel(InputStream in) public static WritableByteChannel newChannel(OutputStream out)
Example 15-4 shows how you might decompress a gzipped file by first decompressing it with a GZipInputStream, then converting this input stream into a ReadableByteChannel. Next, System.out is converted into a WritableByteChannel. Finally, the decompressed data is copied from one channel to another through an intermediate buffer.
Example 15-4. Converting streams to channels
import java.io.*; import java.util.zip.*; import java.nio.*; import java.nio.channels.*; public class NIOUnzipper { public static void main(String[] args) throws IOException { FileInputStream fin = new FileInputStream(args[0]); GZIPInputStream gzin = new GZIPInputStream(fin); ReadableByteChannel in = Channels.newChannel(gzin); WritableByteChannel out = Channels.newChannel(System.out); ByteBuffer buffer = ByteBuffer.allocate(65536); while (in.read(buffer) != -1) { buffer.flip( ); out.write(buffer); buffer.clear( ); } } } |
The while loop relies on Java's promise that every call to write( ) will write all of the requested bytes. While not true of all writable byte channels, this is true of the ones returned by the Channels.newChannel( ) method.
15.3.3. Converting Channels to Readers and Writers
The Channels class also has four methods to convert between WritableByteChannels and Writers and ReadableByteChannels and Readers. We haven't talked about readers and writers yet; that discussion will start in Chapter 20, but in the meantime these methods aren't hard to understand. They work much the same as the methods that convert between streams and channels. However, channels are byte-based and readers and writers are char-based. Therefore, these methods also require you to provide a CharsetEncoder or CharsetDecoder object that will convert between bytes and Java chars. Alternatively, instead of providing an encoder or decoder, you can just give the name of the character set and let Java find the right encoder or decoder object:
public static Reader newReader(ReadableByteChannel channel, String characterSetName) public static Writer newWriter(WritableByteChannel channel, String characterSetName)
Unlike the streams returned by Channels.newInputStream( ) and Channels.newOutputStream( ), the readers and writers returned by Channels.newReader( ) and Channels.newWriter( ) are buffered. You can specify a minimum buffer capacity if you like:
public static Reader newReader(ReadableByteChannel channel, CharsetDecoder decoder, int minimumBufferCapacity) public static Writer newWriter(WritableByteChannel channel, CharsetEncoder encoder, int minimumBufferCapacity)
We'll talk more about CharsetEncoder and CharsetDecoder in Chapter 19.