Writing Files

The java.io.FileOutputStream class is a concrete subclass of java.io.OutputStream that provides output streams connected to files.

public class FileOutputStream extends OutputStream

This class has all the usual methods of output streams, such as write(), flush(), and close(), which are used exactly as they are for any other output stream.

public native void write(int b) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException
public native void close() throws IOException

These are all implemented in native code except for the two multibyte write() methods. These, however, just pass their arguments on to a private native method called writeBytes(), so effectively all these methods are implemented with native code.

There are three main FileOutputStream() constructors, differing primarily in how the file is specified:

public FileOutputStream(String filename) throws IOException
public FileOutputStream(File file) throws IOException
public FileOutputStream(FileDescriptor fd)

The first constructor uses a string containing the name of the file; the second constructor uses a java.io.File object; the third constructor uses a java.io.FileDescriptor object. I will avoid using the second and third constructors until I’ve discussed File objects and file descriptors (Chapter 12). To write data to a file, just pass the name of the file to the FileOutputStream() constructor, then use the write() methods as normal. If the file does not exist, all three constructors will create it. If the file does exist, any data inside it will be overwritten.

A fourth constructor also lets you specify whether the file’s contents should be erased before data is written into it (append == false) or whether data is to be tacked onto the end of the file (append == true). The other three constructors simply overwrite the file; they do not provide an option to append data to the file.

public FileOutputStream(String name, boolean append) throws IOException

Java looks for files in the current working directory. You can write to a file in a different directory by passing a full or relative path to the file from the current working directory. For example, to append data to the \Windows\java\javalog.txt file no matter which directory is current, you would do this:

FileOutputStream fout = 
new FileOutputStream("/Windows/java/javalog.txt", true);

Although Windows uses a backslash as the directory separator, Java still expects you to use a forward slash as in Unix, at least in Java 1.1. Hardcoded pathnames are dangerously platform-dependent. Using this constructor automatically classifies your program as impure Java. We will take this up in more detail in Chapter 12.

Untrusted applets are normally not allowed to read or write files. If an applet tries to create a FileOutputStream, the constructor throws a SecurityException.

The FileOutputStream class has one method that’s not declared in java.io.OutputStream, getFD():

public final FileDescriptor getFD() throws IOException

This method returns the java.io.FileDescriptor object associated with this stream.

The FileOutputStream class also has a protected finalize() method that’s invoked before a FileOutputStream object is garbage-collected. This method ensures that files are properly flushed and closed before the file output stream that opened them is garbage-collected. You normally don’t need to invoke this method explicitly. If you subclass FileOutputStream and override finalize(), the subclass’s finalize() method should invoke this finalize() method by calling super.finalize().

Example 4.2 reads two filenames from the command line, then copies the first file into the second file. The StreamCopier class from Example 3.3 in the last chapter is used to do the actual reading and writing.

Example 4-2. The FileCopier Program

import java.io.*;
import com.macfaq.io.*;

public class FileCopier {

  public static void main(String[] args) {

    if (args.length != 2) {
      System.err.println("Usage: java FileCopier infile outfile");
    }
    try {
      copy(args[0], args[1]);
    }
    catch (IOException e) {System.err.println(e);}
  }

  public static void copy(String inFile, String outFile) 
   throws IOException {

    FileInputStream fin = null;
    FileOutputStream fout = null;
    
    try {
      fin  = new FileInputStream(inFile);
      fout = new FileOutputStream(outFile);
      StreamCopier.copy(fin, fout);
    }
    finally {
      try {
        if (fin != null) fin.close();
      }
      catch (IOException e) {
      }
      try {
        if (fout != null) fout.close();
       }
      catch (IOException e) { }
    }
  }
}

Since we’re no longer writing to System.out and reading from System.in, it’s important to make sure the streams are closed when we’re done. This is a good use for a finally clause, as we need to make sure the files are closed whether or not the reads and writes succeed.

Java is better about closing files than most languages. As long as the VM doesn’t terminate abnormally, the files will be closed when the program exits. Still, if this class is used inside a long-running program like a web server, waiting until the program exits isn’t a good idea; other threads and processes may need access to the files.

There is one bug in this program: it does not behave well if the input and output files are the same. While it would be straightforward to compare the two filenames before copying, this is not safe enough. Once aliases, shortcuts, symbolic links, and other factors are taken into account, a single file may have multiple names. The full solution to this problem will have to wait until Chapter 12, when we discuss canonical paths and temporary files.

Get Java I/O now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.