Chapter 4. File Streams

Until now, most of the examples in this book have used the streams System.in and System.out. These are convenient for examples, but in real life, you’ll more commonly attach streams to data sources like files and network connections. You’ll use the java.io.FileInputStream and java.io.FileOutputStream classes, which are concrete subclasses of java.io.InputStream and java.io.OutputStream, to read and write files. FileInputStream and FileOutputStream provide input and output streams that let you read and write files. We’ll discuss these classes in detail in this chapter; they provide the standard methods for reading and writing data. What they don’t provide is a mechanism for file-specific operations, like finding out whether a file is readable or writable. For that, you may want to look forward to Chapter 12, which talks about the File class itself and the way Java works with files.

Reading Files

java.io.FileInputStream is a concrete subclass of java.io.InputStream. It provides an input stream connected to a particular file.

public class FileInputStream extends InputStream

FileInputStream has all the usual methods of input streams, such as read(), available(), skip(), and close(), which are used exactly as they are for any other input stream.

public native int read() throws IOException
public int read(byte[] data) throws IOException
public int read(byte[] data, int offset, int length) throws IOException
public native long skip(long n) throws IOException
public native int available() throws IOException
public native void close() throws IOException

These methods are all implemented in native code, except for the two multibyte read() methods. These, however, just pass their arguments on to a private native method called readBytes(), so effectively all these methods are implemented with native code. (In Java 2, read(byte[] data, int offset, int length) is a native method that read(byte[] data) invokes.)

There are three FileInputStream() constructors, which differ only in how the file to be read is specified:

public FileInputStream(String fileName) throws IOException
public FileInputStream(File file) throws FileNotFoundException
public FileInputStream(FileDescriptor fdObj)

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. Filenames are platform-dependent, so hardcoded file names should be avoided where possible. Using the first constructor violates Sun’s rules for “100% Pure Java” immediately. Therefore, the second two constructors are much preferred. Nonetheless, the second two will have to wait until File objects and file descriptors are discussed in Chapter 12. For now, I will use only the first.

To read a file, just pass the name of the file into the FileInputStream() constructor. Then use the read() method as normal. For example, the following code fragment reads the file README.TXT, then prints it on System.out:

try {
  FileInputStream fis = new FileInputStream("README.TXT"); 
  int n;     
  while ((n = fis.available()) > 0) {
    byte[] b = new byte[n];
    int result = fis.read(b);
    if (result == -1) break;
    String s = new String(b);
    System.out.print(s); 
  } // End while
} // End try
catch (IOException e) {System.err.println(e);}
System.out.println();

Java looks for files in the current working directory. Generally, this is the directory you were in when you typed java program_name to start running the program. You can open a file in a different directory by passing a full or relative path to the file from the current working directory. For example, to read the file /etc/hosts no matter which directory is current, you can do this:

FileInputStream fis = new FileInputStream("/etc/hosts");

Notice that this code depends on Unix-style pathnames. It is not guaranteed to work on Windows or the Mac, though it might; some runtime environments like Apple’s Macintosh Runtime for Java include extra code to translate from Unix-style filenames to the native style.

If the file you’re trying to read does not exist when the FileInputStream object is constructed, a FileNotFoundException (a subclass of java.io.IOException) is thrown. If for some other reason a file cannot be read—for example, the current process does not have read permission for the file—some other kind of IOException is thrown.

Example 4.1 reads filenames from the command line, then copies the named files onto System.out. The StreamCopier.copy() method from Example 3.3 in the last chapter does the actual reading and writing. Notice that that method does not care that the input is coming from a file or going to the console. It works regardless of the type of the input and output streams it’s copying. It will work equally well for other streams still to be introduced, including ones that did not even exist when StreamCopier was created.

Example 4-1. The FileTyper Program

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

public class FileTyper {

  public static void main(String[] args) {

    if (args.length == 0) {
      System.err.println("Usage: java FileTyper file1 file2 ...");
      return;
    }
    
    for (int i = 0; i < args.length; i++) {
      try {
        typeFile(args[i]);
        if (i+1 < args.length) { // more files to type
          System.out.println();
          System.out.println("------------------------------------");
        }   
      }
      catch (IOException e) {System.err.println(e);}
    }
  }

  public static void typeFile(String filename) throws IOException {

    FileInputStream fin = new FileInputStream(filename);
    StreamCopier.copy(fin, System.out);
    fin.close();

  }
}

Untrusted applets are not usually allowed to read or write files. If your applet tries to create a FileInputStream , the constructor will throw a SecurityException.

The FileInputStream class has one method that’s not declared in the InputStream superclass, getFD().

public final FileDescriptor getFD() throws IOException

This method returns the java.io.FileDescriptor object associated with this stream. File descriptor objects are discussed in Chapter 12. For now, all you can do with this object is use it to create another file stream. FileInputStream also has a protected finalize() method that’s invoked when a FileInputStream object is garbage collected. This method ensures that files are properly closed before the file input stream that opened them is garbage-collected:

protected void finalize() throws IOException

You don’t normally need to invoke this method explicitly, but if you subclass FileInputStream (something I’ve never found a need for), you must invoke super.finalize() from your subclass’s finalize() method.

It is possible to open multiple input streams to the same file at the same time, though it’s rarely necessary to do so. Each stream maintains a separate pointer to the current position in the file. Reading from the file does not change the file in any way. Writing to a file is a different story, as you’ll see in the next section.

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.