Trail: Essential Java Classes
|
Lesson: Reading and Writing (but no 'rithmetic)
|
|
How to Use Pipe Streams
PipedReader and
PipedWriter (and their input and output stream counterparts
PipedInputStream and
PipedOutputStream )
implement the input and output components of a pipe.
Pipes are used to channel the output from one program
(or thread) into the input of another.
Why is this useful?
Consider a class that implements various string manipulation utilities
such as sorting and reversing text. It would be nice if the output of
one of these methods could be used as the input for another so that you
could string a series of method calls together to perform some
higher-order function. For example, you could reverse each word in a
list, sort the words, and then reverse each word again to create a list
of rhyming words.
Without pipe streams, the program would have to store the results
somewhere (such as in a file or in memory) between each step, as shown
here:
 This figure has been reduced to fit on the page. Click the image to view it at its natural size.
With pipe streams, the output from one method
could be piped into the next, as shown in this figure:
Next, we investigate a program that implements what's represented by
the diagram in the previous figure.
This program uses PipedReader and PipedWriter
to connect the input and output of its reverse
and sort methods in
order to create a list of rhyming words. Several classes make up this
program. This section shows and discusses only the elements of the
program that read from and write to the pipes. Follow the code
links presented here to see the whole program.
First, let's look at the calling sequence of the reverse
and sort methods from the main method
in the
RhymingWords
class:
FileReader words = new FileReader("words.txt");
Reader rhymingWords = reverse(sort(reverse(words)));
The innermost call to reverse takes a
FileReader opened on the file
words.txt that
contains a list of words. The return value of reverse is
passed to sort, whose return value is then passed to
another call to reverse.
Let's look at the reverse method; the sort
method is similar and you will understand it once you understand
reverse.
public static Reader reverse(Reader source) {
BufferedReader in = new BufferedReader(source);
PipedWriter pipeOut = new PipedWriter();
PipedReader pipeIn = new PipedReader(pipeOut);
PrintWriter out = new PrintWriter(pipeOut);
new ReverseThread(out, in).start();
return pipeIn;
}
The bold statements in reverse create both ends of a
pipe--a PipedWriter and a PipedReader--
and connects them by constructing
the PipedReader "on" the PipedWriter.
Whatever's written to the
PipedWriter can be read from the PipedReader.
The connection forms a
pipe, as illustrated here:
reverse starts a
ReverseThread
that writes its output to the PipedWriter
and then returns the PipedReader to the caller.
The caller then arranges for a sorting thread to read from it.
The sort method is exactly the same,
except that it creates and starts a
SortThread.
Using Streams to Wrap Other Streams
The reverse method contains some other interesting code;
in particular, these two statements:
BufferedReader in = new BufferedReader(source);
...
PrintWriter out = new PrintWriter(pipeOut);
The first line opens a BufferedReader on source,
the argument to reverse (a Reader).
This essentially "wraps" source in a
BufferedReader.
The program reads from the BufferedReader,
which in turn reads from source.
The program does this so that it can use
BufferedReader's convenient readLine method.
Similarly, the PipedWriter
is wrapped in a PrintWriter
so that the program can use PrintWriter's
convenient println method.
You will often see streams wrapped in this
way so as to combine the various features of the many streams.
Try this:
Write another version of this program that uses input streams and
output streams in place of readers and writers.
See
for the solution.
|