We use a string as our data source by using a StringReader (see Section 20.5.7 on page 523). The
StringReader is then wrapped by our UppercaseConvertor. Reading from the filtered stream
converts all the characters from the string stream into uppercase. For the input "nolowercase" we get the
output:
NO LOWERCASE
You can chain any number of Filter byte or character streams. The original source of input can be a stream
that is not a Filter stream. You can use an InputStreamReader to convert a byte input stream to a
character input stream.
Filter output streams can be chained similarly so that data written to one stream will filter and write data to
the next output stream. All the streams, from the first to the next-to-last, must be Filter output stream
objects, but the last stream can be any kind of output stream. You can use an OutputStreamWriter to
convert a character output stream to a byte output stream.
Not all classes that are Filter streams actually alter the data. Some classes are behavioral filters, such as the
buffered streams you'll learn about next, while others provide a new interface for using the streams, such as
the print streams. These classes are Filter streams because they can form part of a filter chain.
Exercise 20.2: Rewrite the translateByte class as a filter.
Exercise 20.3: Create a pair of Filter stream classes that encrypt bytes using any algorithm you choosesuch
as XORing the bytes with some valuewith your DecryptInputStream able to decrypt the bytes that your
EncryptOutputStream class creates.
Exercise 20.4: Create a subclass of FilterReader that will return one line of input at a time via a method
that blocks until a full line of input is available.
20.5.3. Buffered Streams
The Buffered stream classesBufferedInputStream, BufferedOutputStream,
BufferedReader, and BufferedWriterbuffer their data to avoid every read or write going
directly to the next stream. These classes are often used in conjunction with File streamsaccessing a disk file
is much slower than using a memory buffer, and buffering helps reduce file accesses.
Each of the Buffered streams supports two constructors: One takes a reference to the wrapped stream and
the size of the buffer to use, while the other only takes a reference to the wrapped stream and uses a default
buffer size.
When read is invoked on an empty Buffered input stream, it invokes read on its source stream, fills the
buffer with as much data as is availableonly blocking if it needs the data being waited forand returns the
requested data from that buffer. Future read invocations return data from that buffer until its contents are
exhausted, and that causes another read on the source stream. This process continues until the source stream
is exhausted.
Buffered output streams behave similarly. When a write fills the buffer, the destination stream's write
is invoked to empty the buffer. This buffering can turn many small write requests on the Buffered stream
into a single write request on the underlying destination.
Here is how to create a buffered output stream to write bytes to a file: