|
Articles Index
I/O Stream Classes
By Anil Hemrajani
January 1999
The Java 2 Platform provides several classes for reading and
writing streams of data. While most of these classes can be found
in the java.io package, they are used in various other
APIs such as java.net, java.util,
java.sql, java.lang, java.security,
JFC, CORBA, and others.
This article discusses the concept of writing specialized stream classes
that can process (filter) data in a special fashion. Note, this is an
advanced topic on I/O streams and assumes the reader has some knowledge
of how I/O streams work. For the basics of I/O stream programming, the
difference between byte and character streams, and the concept of stream
chaining, please refer to Programming
with Java I/O Streams.
The Java platform provides several specialized stream classes, the
majority of which exists in the java.io package. These
classes provide a variety of functionality and they usually come in
pairs, that is, one for reading data and the other for writing data.
For example, the java.io.FileReader and
java.io.FileWriter provide file reading and writing
capabilities, while the java.io.StringReader and
java.io.StringWriter provide the ability to use
java.lang.String as a source for reading data from
and writing data to.
Before you decide to write your own specialized stream classes, it might
be worth your while inspecting the stream classes provided with the Java
2 platform. Because, one, there might already be a stream class available
for your purpose, and two, you can use the source and documentation as
examples for writing your own.
To give you a general idea of the various streaming classes available
in the Java platform, the following is a complete list (excluding
deprecated classes) of stream classes available in the java.io
package.
- BufferedInputStream
- BufferedOutputStream
- BufferedReader
- BufferedWriter
- ByteArrayInputStream
- ByteArrayOutputStream
- CharArrayReader
- CharArrayWriter
- DataInputStream
- DataOutputStream
- FileInputStream
- FileOutputStream
- FileReader
- FileWriter
- FilterInputStream
- FilterOutputStream
- FilterReader
- FilterWriter
- InputStream
- InputStreamReader
|
- LineNumberReader
- ObjectInputStream
- ObjectOutputStream
- OutputStream
- OutputStreamWriter
- PipedInputStream
- PipedOutputStream
- PipedReader
- PipedWriter
- PrintStream
- PrintWriter
- PushbackInputStream
- PushbackReader
- RandomAccessFile
- Reader
- SequenceInputStream
- StreamTokenizer
- StringReader
- StringWriter
- Writer
|
Here are some additional stream classes available in the
java.util.zip and java.util.jar packages:
- CheckedInputStream
- CheckedOutputStream
- DeflaterOutputStream
- GZIPInputStream
- GZIPOutputStream
- InflaterInputStream
- ZipInputStream
- ZipOutputStream
- JarInputStream
- JarOutputStream
For more information on the above classes, please refer to the
Java 2
platform documentation. Additionally, you can download
Java 2 software
and unbundle the src.jar to look at the source for the
above classes.
With such a variety of I/O stream classes available in the Java platform,
you might wonder why anyone would ever need to write their own stream
classes. Well, there could be several reasons why, but mainly because your
application might require processing a data stream in a special way for
which the Java 2 platform does not have sufficient classes. The following
are candidates for specialized stream classes.
- Accessing Special
Storage Devices
With so many storage devices available on the market
(such as tape and zip), there might be a need to access
them from a Java application.
- Spell Checker
Java applications could use specialized stream classes
to spellcheck the data in a stream and get back a list
of misspelled words.
- Language
Converter
A stream subclass could be written to convert words in
an input or output stream from one language to another.
- Text Search Screening
Specialized stream classes could be used to search for specific
words and obtain their locations in a data stream. Examples of
uses of such classes could include a find feature in a text,
editor or the ability to block out certain content (for example,
adult content) in a proxy server.
Writing your own stream classes is relatively simple. For reading
character streams, you basically extend the java.io.Reader
class (or java.io.FilterReader); for writing character streams,
you extend the java.io.Writer class (or
java.io.FilterWriter). For byte streams, you extend the
java.io.InputStream (or java.io.FilterInputStream)
for reading streams and java.io.OutputStream (or
java.io.FilterOutputStream) for writing streams.
Note, to understand the difference between byte and character streams,
please refer to the article, Programming
with Java I/O Streams.
When developing specialized stream classes, you can choose to either
directly subclass the top-level classes (InputStream,
OutputStream, Reader, Writer)
or you can subclass the Filter classes
(InputFilterStream, OutputFilterStream,
FilterReader, FilterWriter).
The filter stream classes seem a bit superfluous at times. However, they
were originally designed for convenience, because they provide default
implementations for all the methods found in the top-level classes. This
enables you to override only those methods you need to. For example, if
you extend OutputStream direct, you need to override the
flush() and close() methods even though they
are not abstract methods. This is necessary because these methods have
empty bodies in the OutputStream class. On the other hand,
when you extend the FilterOutputStream class, you generally
end up providing implementations for two write() methods,
versus one if you extended OutputStream direct.
To develop specialized character stream classes, you either subclass the
Reader class for reading data, or the Writer
class for writing data. To develop a Reader subclass, you
must override and provide implementations for the following methods:
int read(char[] cbuf, int off, int len)
void close()
To develop a Writer subclass, you must override and provide
implementations for the following methods:
void write(char[] cbuf, int off, int len)
void close()
void flush()
To develop specialized character stream classes, you either subclass the
InputStream class for reading data or the OutputStream
class for writing data. To develop an InputStream subclass,
you must override and provide implementations for the following method:
int read()
To develop an OutputStream subclass, you must override and
provide implementations for the following method:
void write(int b)
Now that you have a general idea of the variety of situations stream
classes can be written for, it would help to look at some concrete
examples of specialized non-Java stream classes.
The following two examples, TeeOutputStream and
CountReader, provide simple illustrations of writing
specialized stream classes. Later in this section, you will see a
more robust, real-world example using Divya's BackOnline commercial
application.
The following class, TeeOutputStream provides an example of an
OutputStream subclass. The purpose of this class is not only
to write the bytes to the chained output stream, but also to write them to
another output stream, such as the System.out device or a
trace/debug file, for debugging purposes. The functionality
provided by this class is similar to the UNIX tee command.
The code should be fairly easy to follow. Basically, this class overrides
the write(int c), flush() and close()
methods of its parent class, OutputStream. Additionally, this
class provides a main() method for "self-testing"
this class. The write() method performs the task of writing
the data to two streams.
import java.io.* ;
public class TeeOutputStream
extends OutputStream
{
OutputStream tee = null, out = null;
public TeeOutputStream(OutputStream chainedStream,
OutputStream teeStream)
{
out = chainedStream;
if (teeStream == null)
tee = System.out;
else
tee = teeStream;
}
/**
* Implementation for parent's abstract write method.
* This writes out the passed in character to the both,
* the chained stream and "tee" stream.
*/
public void write(int c) throws IOException
{
out.write(c);
tee.write(c);
tee.flush();
}
/**
* Closes both, chained and tee, streams.
*/
public void close() throws IOException
{
flush();
out.close();
tee.close();
}
/**
* Flushes chained stream; the tee stream is flushed
* each time a character is written to it.
*/
public void flush() throws IOException
{
out.flush();
}
/** Test driver */
public static void main(
String args[]) throws Exception
{
FileOutputStream fos =
new FileOutputStream("test.out");
TeeOutputStream tos =
new TeeOutputStream(fos, System.out);
PrintWriter pw =
new PrintWriter(new OutputStreamWriter(tos));
pw.println("Testing line 1");
pw.println("Testing line 2");
pw.close();
}
}
|
The following class, CountReader, provides an example of a
Reader subclass. This class counts the number of characters
and lines in an input stream of characters. At any point in reading the
stream, the get*Count() methods can be used to obtain the
character, word, and, or line count for the data read up to that point.
The functionality provided by this class is similar to the UNIX
wc command.
Like the TeeOutputStream class above, this class should also
be easy to follow. The key method in this example is read(),
which handles the task of counting characters, words, and lines.
import java.io.* ;
public class CountReader
extends Reader
{
Reader in = null;
int read = 0, charCount=0, wordCount = 0,
lineCount = 0;
boolean whiteSpace = true;
public CountReader (Reader r)
{
in = r;
}
/**
* Implementation for parent's read method. Counts
* chars, words, and lines.
*/
public int read(char[] array, int off, int len)
throws IOException
{
if (array == null)
throw new IOException("Null array");
// Do actual read
read = in.read(array, off, len);
// Now count
charCount += read;
// Increment character count
char c;
for (int i=0; i < read; i++)
{
c = array[i];
// Line count
if (c == '\n')
lineCount++;
// Word count
if (Character.isWhitespace(c))
whiteSpace = true;
else
if (Character.isLetterOrDigit(c)
&& whiteSpace)
{
wordCount++;
whiteSpace = false;
}
}
return read;
}
public void close() throws IOException
{
in.close();
}
public int getCharCount() { return charCount; }
public int getWordCount() { return wordCount; }
public int getLineCount() { return lineCount; }
/** Test driver */
public static void main(
String args[]) throws Exception
{
CountReader cr = new CountReader(new
FileReader("CountReader.java"));
char c[] = new char[4096];
int read = 0;
while ((
read = cr.read(c, 0, c.length)) != -1)
System.out.print(new String(c, 0, read));
cr.close();
System.out.println(
"\n\nRead chars: " + cr.getCharCount() +
"\n words: " + cr.getWordCount() +
"\n lines: " + cr.getLineCount());
}
}
|
Divya's 100% Pure Java applet and
application, BackOnline, is a Internet-based backup, briefcase, and
archival application that backs up a user's data files from the client
machine to a server, as portrayed in the following illustration.
BackOnline makes extensive use of streams: everything from File Input/Output
streams, to Socket streams, to its own specialized Protocol and
Encryption/Decryption streams. When a user backs up a file, or
files, in BackOnline, the file is processed via a compression stream,
then an encryption stream, and then sent over the wire on a socket stream.
The server receives this data using socket streams, and stores it in a
file using file streams. For restoring files, this process simply works
in reverse. The streams in use during the backup and restore processing
are shown in the following figure.
Although, most of the stream classes used in BackOnline are ones available
in the Java core APIs (such as socket, file, GZIP compression), there are
two pairs of specialized stream classes written by Divya: one for the protocol
used by the client and server to communicate with each other; the other
for encrypting and decrypting the stream using 56-bit DES encryption.
The application uses a protocol almost identical to HTTP version 1.1.
Each client request (such as file backup) contains a header followed by
a blank line, followed by the data for that request. The stream classes
automatically handle the separation of the header and data portions in
the stream, and provide the appropriate methods to obtain the header
values and read() methods to read the actual data.
Here is the Java source code
for the ProtocolInputStream class, which is used for
reading in the protocol stream. Notice how the read and close methods
override their parent-stream class' methods.
When a user logs into BackOnline, in addition to providing the userid
and password for authentication, they provide an optional encryption
key, like a second password, which is used to encrypt and decrypt the
stream. In other words, the application uses a key/password-based
encryption/decryption scheme. Because this key is always entered by the
user and never physically stored anywhere, it provides an extremely
secure mechanism for transmitting and storing the user's data.
Here is the Java source code for
the CryptOutputStream class, which is used for writing an
encrypted stream. Notice how the Write, Flush
and Close methods override their parent-stream class' methods.
After looking at the source for the TeeOutputStream,
CountReader, ProtocolInputStream, and
CryptOutputStream classes, you should have a very
good idea of how to write your own stream classes.
By now, you probably have an in-depth understanding of how the Java
I/O stream classes work. However, it will still be worth your while
to take a look at the
Java 2 platform
source code, and
documentation
for the various stream classes, especially the classes in the
java.io package.
Remember, while I/O streaming is perhaps not the most glamorous subject
compared to other Java technologies, it is in a sense the "central
nervous system" of the Java platform, because it is used in so many
APIs. Therefore, a thorough understanding of I/O stream classes is essential.
Anil Hemrajani is a senior consultant at Divya
Incorporated, a consulting firm specializing in Java/Internet software
solutions. Anil provides
Java/Internet-based architecture, design and development solutions to
Fortune 500 companies, and occasionally writes articles and speaks at
conferences. He can be reached at
anil@divya.com.
|
|