6.4 File Copying with Buffers
Example 6-4 is another file copying program. Instead
of using
the shortcut FileChannel.transferTo(
) method, this example defines a
generic copy( ) method for copying bytes from one
channel to another and demonstrates the basic
java.nio I/O loop, described here:
- The buffer is filled with bytes from one channel.
- The buffer is flipped, making it ready to be drained. See
Buffer and Buffer.flip( ) for
details. - The buffer is drained by writing bytes from it to another channel.
- The buffer is compacted, discarding bytes that have been drained from
it and shifting remaining bytes to the beginning of the buffer. As
part of this process, the current position of the buffer is reset to
point to the first available byte in the buffer, making the buffer
ready to be filled again. See ByteBuffer.compact(
) for details. If the call to write( )
completely drained the buffer, then clear( ) can
be used instead of compact( ).
This loop continues until the input channel indicates that there are
no more bytes to read (its read( ) method returns
-1) and the buffer is empty.Variants on this basic loop appear in most programs that use the New
I/O API, so it is important to understand it. For clarity, Example 6-4 omits exception-handling and stream-closing
code so that you can focus on the basic loop.
Example 6-4. FileCopy3.java
package je3.nio;
import java.io.*;
import java.nio.*;
import java.nio.channels.*;
public class FileCopy3 {
public static void main(String[ ] args) throws IOException {
// Open file streams and get channels for them.
ReadableByteChannel in = new FileInputStream(args[0]).getChannel( );
WritableByteChannel out;
if (args.length > 1) out = new FileOutputStream(args[1]).getChannel( );
else out = Channels.newChannel(System.out);
// Do the copy
copy(in, out);
// Exception-handling and stream-closing code has been omitted.
}
// Read all available bytes from one channel and copy them to the other.
public static void copy(ReadableByteChannel in, WritableByteChannel out)
throws IOException
{
// First, we need a buffer to hold blocks of copied bytes.
ByteBuffer buffer = ByteBuffer.allocateDirect(32 * 1024);
// Now loop until no more bytes to read and the buffer is empty
while(in.read(buffer) != -1 || buffer.position( ) > 0) {
// The read( ) call leaves the buffer in "fill mode". To prepare
// to write bytes from the buffer, we have to put it in "drain mode"
// by flipping it: setting limit to position and position to zero
buffer.flip( );
// Now write some or all of the bytes out to the output channel
out.write(buffer);
// Compact the buffer by discarding bytes that were written
// and shifting any remaining bytes. This method also
// prepares the buffer for the next call to read( ) by setting the
// position to the limit and the limit to the buffer capacity.
buffer.compact( );
}
}
}
6.4.1 Loop Alternatives
The code shown in Example 6-4 isn't
the only way to express the basic
java.nio channel copying loop.
Following are two variants from the book Java
NIO, by Ron Hitchens (O'Reilly). The
first variant simplifies the exit condition for the loop, by adding a
second loop to drain the buffer when the first loop reaches
end-of-file:
public static void copy1(ReadableByteChannel in, WritableByteChannel out)
throws IOException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while(in.read(buffer) != -1) {
// Prepare the buffer to be drained
buffer.flip( );
// Write to the channel; may block
out.write(buffer);
// If partial transfer, shift remainder down
// If buffer is empty, same as doing clear( )
buffer.compact( );
}
// EOF will leave buffer in fill state
buffer.flip( );
// Make sure that the buffer is fully drained
while (buffer.hasRemaining( )) {
out.write(buffer);
}
}
The second variant of the loop ensures that the buffer is fully
drained on each iteration, so that the call to compact(
) is no longer necessary:
public static void copy2(ReadableByteChannel in, WritableByteChannel out)
throws IOException
{
ByteBuffer buffer = ByteBuffer.allocateDirect(16 * 1024);
while(in.read(buffer) != -1) {
// Prepare the buffer to be drained
buffer.flip( );
// Make sure that the buffer was fully drained
while (buffer.hasRemaining( )) {
out.write(buffer);
}
// Make the buffer empty, ready for filling
buffer.clear( );
}
}