Main Page

Previous Next

Copying Files

You probably don't need a file copy program as your operating system is bound to provide a facility for this. However, it is a useful way of demonstrating how a file channel for any input file can transfer data directly to a file channel for an output file without involving explicit buffers.

A file channel defines two methods for direct data transfer: :

transferTo(long position, long count, WritableByteChannel dst)

Attempts to transfer count bytes from this channel to the channel, dst. Bytes are read from this channel starting at the file position specified by position. The position of this channel is not altered by this operation but the position of dst will be incremented by the number of bytes written. Fewer than count bytes will be transferred if this channel's file has fewer than count bytes remaining, or if dst is non-blocking and has fewer than count bytes free in its system output buffer. The number of bytes transferred is returned as type int.

transferFrom(ReadableByteChannel src, long position, long count)

Attempts to transfer count bytes to this channel from the channel src. Bytes are written to this channel starting at the file position specified by position. The position of this channel is not altered by the operation but the position of src will be incremented by the number of bytes read from it. If position is greater than the size of the file, then no bytes will be transferred. Fewer than count bytes will be transferred if the file corresponding to src has fewer than count bytes remaining in the file or if it is non-blocking and has fewer than count bytes free in its system input buffer. The number of bytes transferred is returned as type int.

A channel that was obtained from a FileInputStream object will only support the transferTo() method. Similarly, a channel that was obtained from a FileOutputStream object will only support the transferFrom() method. Both of these methods can throw any of the following flurry of exceptions:

IllegalArgumentException

Thrown if either count or position is negative.

NonReadableChannelException

Thrown if the operation attempts to read from a channel that was not opened for reading.

NonWritableChannelException

Thrown if the operation attempts to write to a channel that was not opened for writing.

ClosedChannelException

Thrown if either channel involved in the operation is closed.

AsynchronousCloseException

Thrown if either channel is closed by another thread while the operation is in progress.

ClosedByInterruptException

Thrown if another thread interrupts the current thread while the operation is in progress.

IOException

Thrown if some other I/O error occurs.

The value of these methods lies in the potential for using the I/O capabilities of the underlying operating system directly. Where this is possible, the operation is likely to be much faster than copying from one file to another in a loop using the read() and write() methods we have seen.

A file copy program is an obvious candidate for trying out these methods.

Try It Out – Direct Data Transfer between Channels

We will put together a program that will copy the file that is specified by a command line argument. We will copy the file to a backup file that we will create in the same directory as the original. We will create the name of the new file by appending "_backup" to the original file name as many times as necessary to form a unique file name. That operation is a good candidate for writing a helper method:

  // Method to create a unique backup File object  
  public static File createBackupFile(File aFile) {
     aFile = aFile.getAbsoluteFile();          // Ensure we have an absolute path
     File parentDir = new File(aFile.getParent());    // Get the parent directory
     String name = aFile.getName();                   // Get the file name
     int period = name.indexOf('.');           // Find the extension separator
     if(period == -1)                          // If there isn't one
       period = name.length();                 // set it to the end of the string
     String nameAdd = "_backup";               // String to be appended

     // Create a File object that is unique
     File backup = new File(name.substring(0,period) + nameAdd 
                          + name.substring(period));
     while(backup.exists()) {                  // If the name already exists...
        name = backup.getName();               // Get the current name of the file
        period += nameAdd.length();            // Increment separator index    
       backup = new File(parentDir,name.substring(0,period) // add _backup again
                       + nameAdd + name.substring(period));
     }
     return backup;   
  }

This method assumes the argument has already been validated as a real file. After making sure that aFile is not a relative path we extract the basic information we need to create the new file – the parent directory, the file name, and where the period separator is, if there is one. We then create a File object using the original file name with "_backup" appended. The while loop will execute as long as the name already exists as a file, and will append further instances of "_backup" until a unique file name is arrived at.

We can now write the method main() to use this method to create the destination file for the file copy operation:

import java.io.*;
import java.nio.*;
import java.nio.channels.FileChannel;

public class FileCopy {
  public static void main(String[] args) {
    if(args.length==0) {
      System.out.println("No file to copy. Application usage is:\n"+
                         "java -classpath . FileCopy \"filepath\"" );
      System.exit(1);
    }
    File fromFile = new File(args[0]);

    if(!fromFile.exists()) {
      System.out.println("File to copy, "+fromFile.getAbsolutePath()
                       + ", does not exist.");
      System.exit(1);
    }
    
    File toFile = createBackupFile(fromFile);
    FileInputStream inFile = null;
    FileOutputStream outFile = null;
    try {
      inFile = new FileInputStream(fromFile); 
      outFile = new FileOutputStream(toFile);

    } catch(FileNotFoundException e) {
      e.printStackTrace(System.err);
      assert false;
    }

    FileChannel inChannel = inFile.getChannel();    
    FileChannel outChannel = outFile.getChannel(); 

    try {
      int bytesWritten = 0;
      long byteCount = inChannel.size();
      while(bytesWritten<byteCount)
        bytesWritten += inChannel.transferTo(bytesWritten, 
                                             byteCount-bytesWritten,
                                             outChannel); 
      
      System.out.println("File copy complete. " + byteCount 
                       + " bytes copied to " 
                       + toFile.getAbsolutePath());
      inFile.close();
      outFile.close();

    } catch(IOException e) {
      e.printStackTrace(System.err);
      System.exit(1);
    }
    System.exit(0); 
  }
 
  // Code for createBackupFile() goes here...
}

You could try this out by copying the source for the program using the command:

java FileCopy FileCopy.java

You should get output something like:

File copy complete. 3036 bytes copied to D:\Beg Java 1.4\Examples\FileCopy_backup.java

Of course, if the source file layout is different or you have a few more – or less – comments, the number of bytes copied will be different. Also the file path will be your path, not mine. In any event, you should be able to check that the new file's contents are identical to the original.

How It Works

We first obtain the command line argument and create a File object from it with the code:

    if(args.length==0) {
      System.out.println("No file to copy. Application usage is:\n"+
                         "java -classpath . FileCopy \"filepath\"" );
      System.exit(1);
    }
    File fromFile = new File(args[0]);

If there's no command line argument we supply a message explaining how to use the program.

Next we verify that this is a real file:

if(!fromFile.exists()) {
  System.out.println("File to copy, "+fromFile.getAbsolutePath()
                   + ", does not exist.");
      System.exit(1);
}

If it isn't, there's nothing we can do, so we bail out of the program.

Creating a File object for the backup file is a piece of cake:

    File toFile = createBackupFile(fromFile);

We saw how this method works earlier.

We now create a pair of file stream objects to work with:

FileInputStream inFile = null;
FileOutputStream outFile = null;
try {
  inFile = new FileInputStream(fromFile); 
  outFile = new FileOutputStream(toFile);

} catch(FileNotFoundException e) {
  e.printStackTrace(System.err);
  assert false;
}

Since we checked the File objects, we know we won't see a FileNotFoundException being thrown but we still must provide for the possibility. Of course, the FileInputStream object corresponds to the file name entered on the command line. Creating the FileOutputStream object will result in a new empty file being created, ready for loading with the data from the input file.

Next we get the channel for each file from the file streams:

FileChannel inChannel = inFile.getChannel();    
FileChannel outChannel = outFile.getChannel(); 

Once we have the channel objects we transfer the contents of the input file to the output file like this:

try {
  int bytesWritten = 0;
  long byteCount = inChannel.size();
  while(bytesWritten<byteCount)
    bytesWritten += inChannel.transferTo(bytesWritten, 
                                         byteCount-bytesWritten,
                                         outChannel); 
      
  System.out.println("File copy complete. " + byteCount 
                   + " bytes copied to " + toFile.getAbsolutePath());
  inFile.close();
      outFile.close();

} catch(IOException e) {
  e.printStackTrace(System.err);
  System.exit(1);
}

The data is copied using the transferTo() method for inChannel. You could equally well use the transferFrom() method for outChannel. The chances are the transferTo() method will transfer all the data in one go. The while loop is there just in case it doesn't. The loop condition checks whether the number of bytes written is less than the number of bytes in the file. If it is, the loop executes another transfer operation for the number of bytes left in the file with the file position specified as the number of bytes written so far.

Previous Next
JavaScript Editor Java Tutorials Free JavaScript Editor