Session 32: Files

The File class (Section 14.2)
    File query methods
    File manipulation methods
The FileOutputStream class (Section 14.3)
    Constructors
    Writing methods
The FileInputStream class (Section 14.3)
    Constructors
    Reading methods
Case study: Copying a file

The File class

Textbook: Section 14.2

Java has a large library of classes in the java.io package related to working with files. It's quite extensive and confusing. Today we're going to investigate three of the basic classes: File, FileOutputStream, and FileInputStream.

A File object represents the name of a file on the disk. It allows you to work with the file as an entire object - not looking inside the file, but asking questions about the file's attributes or doing something that works with the file as a whole.

The most basic constructor for a File takes a string as parameter giving the complete location of where the file is located.

File(String path_name)
Creates an object representing the file name based on the path provided by path_name.

For example, you might have the following.
File source = new File("CopyFile.java");
This creates a File variable source, representing the file name ``CopyFile.java''. The parameter names the file just as you would name regular files. In Unix, a path name beginning with slash (`/') is relative to the root directory, and otherwise it is relative to the current directory (from which you invoked the program). You can name directories to move down the file hierarchy (or use the ``..'' directory to move up the hierarchy).
File source2 = new File("CS160/Lab9/CopyFile.java");
File source3 = new File("/usr/people/classes/Java/csbsju");

The File object represents a file's name, not the file itself. Thus, you can create a File object to represent a name, even if a file of that name doesn't exist.

File query methods

The first set of methods that you can perform on a File object relate to answering questions about the file.

boolean exists()
Returns true if the file exists in the file system.
boolean canWrite()
Returns true if your program can write into the file.
boolean canRead()
Returns true if your program can read from the file.
String getName()
Returns the name of the file (leaving aside the names of directories in which the file lies).
long length()
Returns the number of bytes in the file.

For example:

if(!source.exists()) {
    IO.println("The file ``" + source.getName() + "'' doesn't exist.");
    System.exit(-1);
} else if(!source.canRead()) {
    IO.println("The file ``" + source.getName() + "'' exists, "
        + "but it isn't accessible.");
    System.exit(-1);
}

File manipulation methods

There are also some for manipulating the file whole.

boolean delete()
Deletes the file, returning true if successful.
boolean renameTo(File dest)
Renames the file to the name specified by dest.

The FileOutputStream class

Textbook: Section 14.3

A FileOutputStream object represents a file into which you can write bytes. It's for writing raw bytes - we'll get into more sophisticated objects for writing characters or writing more complex items later.

Constructors

When you create a FileOutputStream object, the file is opened for writing. The file is set up so that the first write occurs at the first byte of the file - thus, effectively, when you create a FileOutputStream object, the file it represents is immediately cleared.

FileOutputStream(File file) throws FileNotFoundException
Creates a FileOutputStream object for the specified file. (It throws FileNotFoundException if the specified file isn't writeable.)
Notice that this method throws an exception. This means that whenever you try to create a FileOutputStream object, you'll have to deal with the possibility that it raises an exception. Exceptions become a huge pain when you begin to deal with files, since so many things can go wrong.

A FileNotFoundException is an inappropriate name for what this method does, since if the file doesn't exist, it goes ahead and creates one. This exception is raised when creating the file is impossible - for example, if the file exists, but you don't have write permission for it. Or if it doesn't exist, and you don't have permission to create a new file in the directory.

Writing methods

As you write into the file, the FileOutputStream object tracks where you currently are in the file. This is called the file pointer.

void write(byte[] b) throws IOException
Writes the bytes of b into the file just after the location where it last wrote.
void write(byte[] b, int offs, int num) throws IOException
Writes num bytes from b, beginning at index offs in the array, into the file just after the location where it last wrote.
void close() throws IOException
Closes the file, ensuring that everything is saved on the disk and making the object ineligible for use in further writes.
It's important that you close the file once you're done. This is because the program may try to buffer data in memory - trying to conserve the number of times it writes to disk, since disks are so slow. If you don't close the file, and the program doesn't finish properly, then it may run into a situation where the buffer is lost.

Additionally, operating systems typically restrict each program on how many files they can have open at once. So if you don't close it, but your program tries to open several files later on, you may find that on the 64th file the program is suddenly no longer saving.

The FileInputStream class

Textbook: Section 14.3

The FileInputStream class is basically the opposite of the FileOutputStream class - a FileInputStream object is for reading bytes from a file.

Constructors

FileInputStream(File file) throws FileNotFoundException
Creates a FileInputStream object for the specified file. (It throws FileNotFoundException if the specified file doesn't exist or isn't readable.)

Reading methods

int read(byte[] b) throws IOException
Attempts to fill the array b with bytes from the file, returning the number of bytes successfully read, or returning -1 if the end of the file has already been reached.
void close() throws IOException
Closes the file for reading, making further reads from the object invalid.
Closing a FileInputStream file isn't as important as closing a FileOutputStream file, since there's no chance that data will be lost if the program aborts abnormally. Still, it's a good habit to get into - it saves you from problems with getting too many files open at once.

Case study: Copying a file

Let's look at a program that illustrates these three classes at work. This program copies one file into another location. You would invoke it via the command line; for example, if you wanted to copy the file ``CopyFile.java'' into a file named ``CopyFile.bak'', you would type the following at the Unix prompt.

% java CopyFile CopyFile.java CopyFile.bak
  1  import java.io.*;
    
  2  public class CopyFile {
  3      public static void main(String[] args) {
  4          if(args.length != 2) {
  5              System.out.println("usage: java CopyFile source dest");
  6              return;
  7          }
    
  8          File src_f = new File(args[0]);
  9          File dst_f = new File(args[1]);
 10          if(!src_f.exists() || !src_f.canRead()) {
 11              System.out.println("cannot find source: " + src_f.getName());
 12              return;
 13          } else if(dst_f.exists()) {
 14              System.out.println("destination file " + dst_f.getName()
 15                  + " already exists");
 16              return;
 17          }
    
 18          try {
 19              FileInputStream src = new FileInputStream(src_f);
 20              FileOutputStream dst = new FileOutputStream(dst_f);
 21              byte[] buffer = new byte[512];
    
 22              while(true) {
 23                  int count = src.read(buffer);
 24                  if(count == -1) break;
 25                  dst.write(buffer, 0, count);
 26              }
    
 27              src.close();
 28              dst.close();
 29          } catch(IOException e) {
 30              System.out.println("Error during copy: " + e.getMessage());
 31              if(dst_f.exists()) dst_f.delete();
 32          }
 33      }
 34  }

One non-obvious thing about this program is the try block: We know the FileInputStream and FileOutputStream constructor methods throw a FileNotFoundException, and yet the only exception we catch is an IOException object. The reason we could get away with this is that FileNotFoundException is a subclass of IOException, so that, if the constructor invoked in line 19 throws a FileNotFoundException, the catch clause of line 29 would still catch it, since the exception is also an IOException.