The JavaTM Tutorial
Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Trail: Essential Java Classes
Lesson: I/O: Reading and Writing (but no 'rithmetic)

How to Write Your Own Filter Streams

Following are the steps to take when you are writing your own filtered input and output streams:
  1. Create a subclass of FilterInputStream and FilterOutputStream. Input and output streams often come in pairs, so it's likely that you will need to create both input and output versions of your filter stream.
  2. Override the read and write methods, if you need to.
  3. Provide any new methods.
  4. Make sure that the input and output streams work together.
This section shows you how to implement your own filter streams, presenting an example that implements a matched pair of filter input and output streams.

Both the input and the output streams use a checksum class to compute a checksum on the data written to or read from the stream. The checksum is used to determine whether the data read by the input stream matches that written by the output stream.

Four classes and one interface make up this example program:

Except for CheckedIODemo, the classes in this example are based on classes, which are now members of the java.util.zip package, written by David Connelly.

The CheckedOutputStream Class

The CheckedOutputStream (in a .java source file) class, a subclass of FilterOutputStream, computes a checksum on data as it is being written to the stream. When creating a CheckedOutputStream, you must use its only constructor:
public CheckedOutputStream(OutputStream out, Checksum cksum) {
    super(out);
    this.cksum = cksum;
}
This constructor takes an OutputStream argument and a Checksum argument. The OutputStream argument is the output stream that this CheckedOutputStream should filter. The Checksum argument is an object that can compute a checksum. CheckedOutputStream initializes itself by calling its superclass constructor and initializing a private variable, cksum, with the Checksum object. The CheckedOutputStream uses cksum to update the checksum each time data is written to the stream.

CheckedOutputStream needs to override FilterOutputStream's write methods so that each time the write method is called, the checksum is updated. FilterOutputStream defines three versions of the write method. CheckedOutputStream overrides all three of these methods with the following code:

public void write(int b) throws IOException {
    out.write(b);
    cksum.update(b);
}

public void write(byte[] b) throws IOException {
    out.write(b, 0, b.length);
    cksum.update(b, 0, b.length);
}

public void write(byte[] b, int off, int len) throws IOException {
    out.write(b, off, len);
    cksum.update(b, off, len);
}
The implementations of these three write methods are straightforward: Write the data to the output stream that this filtered stream is attached to, and then update the checksum.

The CheckedInputStream Class

The class CheckedInputStream (in a .java source file) is similar to the CheckedOutputStream class. A subclass of FilterInputStream, it computes a checksum on data as it is read from the stream. When creating a CheckedInputStream, you must use its only constructor:
public CheckedInputStream(InputStream in, Checksum cksum) {
    super(in);
    this.cksum = cksum;
}
This constructor is similar to CheckedOutputStream's.

Just as CheckedOutputStream needed to override FilterOutputStream's write methods, CheckedInputStream must override FilterInputStream's read methods so that each time the read method is called, the checksum is updated. As with FilterOutputStream, FilterInputStream defines three versions of the read method. CheckedInputStream overrides all of them by using the following code:

public int read() throws IOException {
    int b = in.read();
    if (b != -1) {
        cksum.update(b);
    }
    return b;
}

public int read(byte[] b) throws IOException {
    int len;
    len = in.read(b, 0, b.length);
    if (len != -1) {
        cksum.update(b, 0, b.length);
    }
    return len;
}

public int read(byte[] b, int off, int len) throws IOException {
    len = in.read(b, off, len);
    if (len != -1) {
        cksum.update(b, off, len);
    }
    return len;
}
The implementations of these three read methods are straightforward: Read the data from the input stream that this filtered stream is attached to, then if any data was actually read, update the checksum.

The Checksum Interface and the Adler32 Class

The Checksum (in a .java source file) interface defines four methods for checksum objects to implement. These methods reset, update, and return the checksum value. You could write a Checksum class that computes a specific type of checksum such as the CRC-32 checksum. Note that inherent in the checksum is the notion of state. The checksum object doesn't just compute a checksum in one pass. Rather, the checksum is updated each time information is read from or written to the stream for which this object computes a checksum. If you want to reuse a checksum object, you must reset it.

For this example, we implemented the checksum Adler32 (in a .java source file), which is almost as reliable as a CRC-32 checksum but can be computed more quickly.

A Program for Testing

The last class in the example, CheckedIODemo (in a .java source file), contains the main method for the program:
import java.io.*;

public class CheckedIODemo {
    public static void main(String[] args) throws IOException {

       Adler32 inChecker = new Adler32();
       Adler32 outChecker = new Adler32();
       CheckedInputStream in = null;
       CheckedOutputStream out = null;

       try {
           in = new CheckedInputStream(
			   new FileInputStream("farrago.txt"),
			   inChecker);
           out = new CheckedOutputStream(
			    new FileOutputStream("outagain.txt"),
			    outChecker);
       } catch (FileNotFoundException e) {
           System.err.println("CheckedIODemo: " + e);
           System.exit(-1);
       } catch (IOException e) {
           System.err.println("CheckedIODemo: " + e);
           System.exit(-1);
       }

       int c;

       while ((c = in.read()) != -1)
          out.write(c);

       System.out.println("Input stream check sum: " +
			  inChecker.getValue());
       System.out.println("Output stream check sum: " +
			  outChecker.getValue());

       in.close();
       out.close();
    }
}
The main method creates two Adler32 checksum objects, one each for a CheckedOutputStream and a CheckedInputStream. This example requires two checksum objects because the checksum objects are updated during calls to read and write and those calls are occurring concurrently.

Next, main opens a CheckedInputStream on a small text file, farrago.txt (in a .java source file), and a CheckedOutputStream on an output file named outagain.txt, which doesn't exist until you run the program for the first time.

The main method reads the text from the CheckedInputStream and simply copies it to the CheckedOutputStream. The read and write methods use the Adler32 checksum objects to compute a checksum during reading and writing. After the input file has been completely read and the output file has been completely written, the program prints out the checksum for both the input and output streams (which should match) and then closes them both.

When you run CheckedIODemo, you should see this output:

Input stream check sum: 736868089
Output stream check sum: 736868089


Previous Page Lesson Contents Next Page Start of Tutorial > Start of Trail > Start of Lesson Search
Feedback Form

Copyright 1995-2004 Sun Microsystems, Inc. All rights reserved.