Previous | Next | Trail Map | Writing Java Programs | Input and Output Streams


Writing Your Own Filtered Streams

The following is a list of steps to take when writing your own filtered input and output streams: This page shows you how to implement your own filtered streams through an example that implements a matched pair of filtered input and output streams. Both the input and the output stream use a checksum class to compute a checksum on the data written to or read from the stream. The checksum can be 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: the CheckedOutputStream and CheckedInputStream classes which are the filtered input and output stream classes, the Checksum interface and the Adler32 class which are used to compute a checksum for the streams, and finally, the main program, CheckedIOTest.

The CheckedOutputStream Class

The CheckedOutputStream class is a subclass of FilterOutputStream that computes a checksum on data as it's 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 is intended to filter. The Checksum argument is some object that can compute a checksum. CheckedOutputStream initializes itself by calling its super constructor and initializing a private variable, cksum with the Checksum object. The CheckedOutputStream uses cksum to upate 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

  1. write(int i)
  2. write(byte b[])
  3. write(byte b[], int offset, int length)
CheckedOutputStream only overrides versions #1 and #3 of the write() method because version #2 is implemented by calling #3.
public void write(int b) throws IOException {
    out.write(b);
    cksum.update(b);
}

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 two write() methods are straightforward: write the data to the output stream that this filtered stream is attached to, then update the checksum.

The CheckedInputStream Class

The CheckedInputStream class is very similar to the CheckedOutputStream class. CheckedInputStream is a subclass of FilterInputStream that computes a checksum on data as it's being 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 takes an InputStream argument and a Checksum argument. The InputStream argument is the input stream that this CheckedInputStream is intended to filter. The Checksum argument is some object that can compute a checksum. CheckedInputStream initializes itself by calling its super constructor and initializing a private variable, cksum with the Checksum object. The CheckedInputStream uses cksum to upate the checksum each time data is read from the stream.

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 but CheckedInputStream only needs to override two of them.

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

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 two read() methods are straightforward: read the data from the input stream that this filtered stream is attached to, if any data was actually read then update the checksum.

The Checksum Interface and the Adler32 Class

The Checksum interface defines four methods for checksum objects to implement; these methods compute, reset, and return the checksum value. You could write a Checksum class that computes a specific type of checksum such as the CRC-32 checksum.

For this example, we implemented the Adler32 checksum which is almost as reliable as a CRC-32 checksum but can be computed much faster.

A Main Program for Testing

The last class in the example, CheckedIOTest, contains the main() for the program.
import java.io.*;

class CheckedIOTest {
    public static void main(String args[]) {

	Adler32 inChecker = new Adler32();
	Adler32 outChecker = new Adler32();
	CheckedInputStream cis = null;
	CheckedOutputStream cos = null;

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

	try {
	    int c;

	    while ((c = cis.read()) != -1) {
	       cos.write(c);
	    }

	    System.out.println("Input stream check sum: " + cis.getChecksum().getValue());
	    System.out.println("Output stream check sum: " + cos.getChecksum().getValue());

	    cis.close();
	    cos.close();
	} catch (IOException e) {
	    System.err.println("CheckedIOTest: " + e);
	}
    }
}
The main() method creates two Adler32 checker objects, one each for a CheckedOutputStream and a CheckedInputStream. Next, main() opens a CheckedInputStream on a small text file, farrago.txt, and a CheckedOutputStream on an output file named "outagain.txt".

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, the program prints out the checksum for both the input and output streams (which should match) and then closes them both.

When you run CheckedIOTest, you should see this output:

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


Previous | Next | Trail Map | Writing Java Programs | Input and Output Streams