The Filter Stream Classes

java.io.FilterInputStream and java.io.FilterOutputStream are concrete superclasses for input and output stream subclasses that somehow modify or manipulate data of an underlying stream:

public class FilterInputStream extends InputStream public class FilterOutputStream extends OutputStream

Each of these classes has a single protected constructor that specifies the underlying stream from which the filter stream reads or writes data:

protected FilterInputStream(InputStream in) protected FilterOutputStream(OutputStream out)

These constructors set protected InputStream and OutputStream fields, called in and out, inside the FilterInputStream and FilterOutputStream classes, respectively:

protected InputStream in protected OutputStream out

Since the constructors are protected, only subclasses can create filter streams. Each subclass implements a particular filtering operation. Most of the time, references to a filter stream are either references to a more specific subclass such as BufferedInputStream or they're polymorphic references to InputStream or OutputStream with no hint of the filter remaining. In other words, it's rare to declare a variable with the explicit type FilterInputStream or FilterOutputStream.

Beyond the constructors, both FilterInputStream and FilterOutputStream declare exactly the methods of their respective superclasses. For FilterInputStream, these are:

public int read( ) throws IOException public int read(byte[] data) throws IOException public int read(byte[] data, int offset, int length) throws IOException public long skip(long n) throws IOException public int available( ) throws IOException public void close( ) throws IOException public void mark(int readlimit) public void reset( ) throws IOException public boolean markSupported( )

For FilterOutputStream, these are:

public 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 void flush( ) throws IOException public void close( ) throws IOException

Each of these methods merely passes its arguments to the corresponding method in the underlying stream. For example, the skip( ) method in FilterInputStream behaves like this:

public long skip(long n) throws IOException { in.skip(n); }

The close( ) method in FilterOutputStream behaves like this:

public void close( ) throws IOException { out.close( ); }

Thus, closing a filter stream closes the underlying stream. You cannot close one filter stream and then open up another on the same underlying stream, nor can you close one filter stream in a chain but still read from the underlying stream or other streams in the chain. Attempting to do so throws an IOException. Once a stream is closedno matter which filter stream it's chained toit's closed for good.

Since the constructors are protected, you don't use these classes directly. Instead, you create subclasses and use those. Since FilterOutputStream does not have a no-argument constructor, it's essential to give all subclasses explicit constructors and use super( ) to invoke the FilterOutputStream constructor. Your subclass will probably also want to override the write(int b) and write(byte[] data, int offset, int length) methods to perform its filtering. The write(byte[] data) method merely invokes write(data, 0, data.length), so if you've overridden the three-argument write( ) method, you probably don't need to override write(byte[] data) as well. Depending on circumstances, you may or may not need to override some of the other methods.

The PrintableOutputStream class shown in Example 6-1 is a subclass of FilterOutputStream that truncates all data to the range of printable ASCII characters: byte values 32-126, plus 9, 10, and 13 (tab, linefeed, and carriage return). Every time a byte in that range is passed to write( ), it is written onto the underlying output stream, out. Every time a byte outside that range is passed to write( ), a question mark is written onto the underlying output stream, out. Among other things, this class provides a quick and dirty way to read ASCII string literals embedded in a .class or .exe file.

Example 6-1. The PrintableOutputStream class

package com.elharo.io; import java.io.*; public class PrintableOutputStream extends FilterOutputStream { public PrintableOutputStream(OutputStream out) { super(out); } public void write(int b) throws IOException { // carriage return, linefeed, and tab if (b == ' ' || b == ' ' || b == ' ') out.write(b); // non-printing characters else if (b < 32 || b > 126) out.write('?'); // printing, ASCII characters else out.write(b); } public void write(byte[] data, int offset, int length) throws IOException { for (int i = offset; i < offset+length; i++) { this.write(data[i]); } } }

To use this class, or any other filter output stream, you must chain it to another stream that actually writes the bytes to their eventual target. For example, to chain a PrintableOutputStream to System.out, you would write:

PrintableOutputStream pos = new PrintableOutputStream(System.out);

If the filter stream subclass only overrides methods from the superclass and does not add any, it's common to just declare the variable as type OutputStream or InputStream. For example, the previous statement can be rewritten like this:

OutputStream out = new PrintableOutputStream(System.out);

Sometimes the underlying stream is created directly inside the constructor:

OutputStream out = new PrintableOutputStream(new FileOutputStream("data.txt"));

However, the sheer length of the stream class names tends to make this style of coding inconvenient.

Multiple streams can be chained together in sequence to get the benefits of each. For example, to create a buffered, printable file output stream, you would chain a file output stream to a buffered output stream, which you'd then chain to a printable output stream. For example:

FileOutputStream fout = new FileOutputStream("data.txt"); BufferedOutputStream bout = new BufferedOutputStream(fout); PrintableOutputStream pout = new PrintableOutputStream(bout);

Sometimes this is done using only a single OutputStream variable:

OutputStream out = new FileOutputStream("data.txt"); out = new BufferedOutputStream(out); out = new PrintableOutputStream(out);

This keeps you from accidentally writing onto or reading from anything but the last stream in the chain. There are reasons you might sometimes need to read from or write to a stream deeper in the chain, but such reasons are unusual, and you shouldn't do it by accident.

Example 6-2 uses the PrintableOutputStream class to extract ASCII strings from a file. First it chains either System.out or a file output stream to a printable output stream, then it opens a file input stream from a file named on the command line and copies it into the printable output stream, thereby converting it to printable ASCII characters.

Example 6-2. The StringExtractor class

import com.elharo.io.*; import java.io.*; public class StringExtractor { public static void main(String[] args) { if (args.length < 1) { System.out.println("Usage: java StringExtractor inFile"); return; } try { InputStream in = new FileInputStream(args[0]); OutputStream out; if (args.length >= 2) { out = new FileOutputStream(args[1]); } else out = System.out; // Here's where the output stream is chained // to the ASCII output stream. PrintableOutputStream pout = new PrintableOutputStream(out); for (int c = in.read(); c != -1; c = in.read( )) { pout.write(c); } out.close( ); } catch (FileNotFoundException e) { System.out.println("Usage: java StringExtractor inFile outFile"); } catch (IOException ex) { System.err.println(ex); } } }

Here's the output produced by running StringExtractor on its own .class file:

$ java StringExtractor StringExtractor.class ???????.?D ???? ?????? ?!?"??# ???$??% ???$??& ? ?' ?(?) ? ?* ?+?,??-??.??/ ???0 ?!?1??2??3??????( )V???Code???LineNumberTable???main??? ([Ljava/lang/String;)V??SourceFile???StringExtractor.java ???????4??5?6??"Usage: java StringExtractor inFile??7??8?9???java/io/FileInputStream????9??? java/io/FileOutputStream??#com/elharo/io/PrintableOutputStream????:??;?? ????@??A?????java/io/FileNotFoundException?? *Usage: java StringExtractor inFile outFile???java/io/IOException??B?6??8?C???StringExtractor???java/lang/Object??? java/lang/System???out???Ljava/io/PrintStream;???java/io/PrintStream???println??? (Ljava/lang/String;)V???(Ljava/io/OutputStream;)V???java/io/InputStream???read??? ( )I???write???(I)V???java/io/OutputStream???close???err???(Ljava/lang/Object;)V ?!????????????????????????????????*????????????????????? ???????????????????r*?????????????????Y*?2???L*????????Y*?2???M??????M?? Y,?? ???L???????????L???+?????????Z?]?????Z?i?????????N????????? ???

Although a lot of information is clearly lost in this translation, a surprising amount is retainedyou have every string literal in the file and the names of all the classes and methods referenced by this class.

Filter input streams are created similarly. Since FilterInputStream does not have a no-argument constructor, all subclasses require explicit constructors and must use super( ) to invoke the FilterInputStream constructor. The subclass overrides the read( ) and read(byte[] data, int offset, int length) methods in order to do the actual filtering. The read(byte[] data) method merely invokes read(data, 0, data.length), so if you've overridden the three-argument read( ) method, you probably don't need to override read(byte[] data) as well. Depending on circumstances, you may or may not need to override some of the other methods. For example, the PrintableInputStream class shown in Example 6-3 truncates all data read to the range of printable ASCII characters. As with PrintableOutputStream, any character not in that range is replaced by a question mark.

Example 6-3. The PrintableInputStream class

package com.elharo.io; import java.io.*; public class PrintableInputStream extends FilterInputStream { public PrintableInputStream(InputStream in) { super(in); } public int read( ) throws IOException { int b = in.read( ); // printing, ASCII characters if (b >= 32 && b <= 126) return b; else if (b == ' ' || b == ' ' || b == ' ') return b; // nonprinting characters else return '?'; } public int read(byte[] data, int offset, int length) throws IOException { int result = in.read(data, offset, length); for (int i = offset; i < offset+result; i++) { // Do nothing with the printing characters. if (data[i] == ' '|| data[i] == ' ' || data[i] == ' ' || data[i] == -1) ; // nonprinting characters else if (data[i] < 32 || data[i] > 126) data[i] = (byte) '?'; } return result; } }

Категории