Section 8.2
Streams


WITHOUT THE ABILITY TO INTERACT WITH the rest of the world, a program would be useless. The interaction of a program with the rest of the world is referred to as input/output or I/O. Historically, one of the hardest parts of programming language design has been coming up with good facilities for doing input and output. A computer can be connected to many different types of input and output devices. If a programming language had to deal with each type of device as a special case, the complexity would be overwhelming. One of the major achievements in the history of programming has been to come up with good abstractions for representing I/O devices. In Java, the I/O abstractions are called streams. This section is an introduction to streams, but it is not meant to cover them in full detail. See the official Java documentation for more information. Also note that a few important general facts about streams are deferred until the next two sections of this chapter, where they are actually used.

There are two types of streams, input streams and output streams. In Java, these are represented by the classes InputStream and OutputStream. A program can read data from an InputStream. It can write data to an OutputStream. Each of these classes have several subclasses that provide various types of I/O facilities. The stream classes are defined in the package java.io. You must import the classes from this package if you want to use them in your program.

Streams are not used in Java's graphical user interface, which has its own form of I/O. But they necessary for working with files (using the classes FileInputStream and FileOutputStream) and for doing communication over a network. They can be also used for communication between two concurrently running threads.

Java's standard packages include a standard input stream and a standard output stream, which are meant for basic communication with the user. (In fact, the proper definition of a "user" is: a particularly slow and unreliable input/output device that is attached by default to the standard input and output streams.) These standard streams are objects belonging to the classes InputStream and PrintStream. PrintStream is a subclass of OutputStream. The standard stream objects are referenced by the static variables System.in and System.out in the class java.lang.System. You have already seen how methods belonging to the object System.out can be used to output information to the user. Similarly, System.in can be used to read characters typed by the user.

The beauty of the stream abstraction is that it is as easy to write data to a file or to send data over a network as it is to print information on the screen. In fact, you can use a PrintStream object in all three cases and use simple PrintStream methods like println().


The basic classes InputStream and OutputStream provide only very primitive I/O operations, which treat data as a stream of uninterpreted bytes. InputStream includes the instance method

         public int read() throws IOException

for reading one byte of data (a number in the range 0 to 255) from an input stream. If the end of the input stream is encountered, the read() method will return the value -1 instead. In the case where the data being read is ordinary ASCII text, the value returned can be typecast to type char (after checking to make sure that the value is not -1, of course). InputStream provides no convenient methods for reading other types of data from a stream. Note that read() will throw an IOException if some error is encountered during the read operation. Since IOException is one of the exception classes that requires mandatory exception-handling, this means that you can't use the read() method except inside a try statement or in a subroutine that is itself declared with a "throws IOException" clause.

The primitive output operation provided by the class OutputStream is

          public void write(int b) throws IOException

This method outputs one byte of data to the output stream. The parameter b should be in the range 0 to 255. (To be more exact, no matter what the value of b, only the last 8 bits of the 32-bit integer value are output.)

You will probably use these primitive I/O methods only rarely (although the read() method is worth using in some cases). In fact, you cannot even directly create instances of InputStream and OutputStream, since they are abstract classes.

The subclasses of the two basic stream classes provide more useful I/O methods. One of the neat things about Java's I/O package is that it lets you add capabilities to a stream by "wrapping" it in another object that provides those capabilities. The wrapper object is still considered to be a stream, so you can read from or write to it -- but you can do so using fancier operations than those available for basic streams. Objects that can be used as wrappers in this way belong to subclasses of FilterInputStream or FilterOutputStream. By writing new subclasses of these classes, you can make your own I/O filters to provide any style of I/O that you want.

For example, PrintStream is a subclass of FilterOutputStream that provides convenient methods for outputting ASCII-text representations of all of Java's basic data types. If you have an object belonging to the OutputStream class, or any of its subclasses, and you would like to use PrintStream methods to write to that OutputStream, all you have to do is wrap the OutputStream in a PrintStream object. You do this by constructing a new PrintStream object, using the OutputStream as input to the constructor. For example, if dataSink is of type OutputStream then you could say

       PrintStream printableDataSink = new PrintStream(dataSink);

When you output data to printableDataSink, using PrintStream's advanced data output methods, that data will go to exactly the same place as data written directly to dataSink. You've just provided a better interface to the same output stream.

For the record, the output methods of the PrintStream class include:

       public void print(String s)   // methods for outputting
       public void print(char c)     //    standard data types
       public void print(int i)      //    to the stream
       public void print(long l)
       public void print(float f)
       public void print(double d)
       public void print(boolean b)
       
       public void println()   // output a carriage return to the stream
       
       public void println(String s)    // these methods are identical
       public void println(char c)      //    to the previous set, 
       public void println(int i)       //    except that the output
       public void println(long l)      //    value is followed by
       public void println(float f)     //    a carriage return
       public void println(double d
       public void println(boolean b)

Note that none of these methods will ever throw an IOException. Instead, the PrintStream class includes the method

       public boolean checkError()

which will return true if any error has been encountered while writing to the stream. The PrintStream class catches any IOExceptions internally, and sets the value of an internal error flag if one occurs. The checkError() method can be used to check the error flag. This allows you to use PrintStream methods without worrying about catching exceptions. On the other hand, to write a fully robust program, you should call CheckError() to test for possible errors every time you use a PrintStream method.


You might wonder why the PrintStream methods are not simply included in the basic output class, OutputStream. The reason is that PrintStream makes one very big assumption: that the output stream is meant to be human-readable. The PrintStream methods convert the internal binary-number representations of data into ASCII-text representations that are meaningful to human readers. However, if the output data is really meant to be read later by a computer, the computer will have to convert the data back into its internal format. These conversions between internal format and ASCII text are wasteful and inefficient for data that is never meant to be read by humans. In fact, many data files are not written in human-readable form. Such files look like gibberish if you try to interpret them as ASCII text. Since many streams do not use ASCII-text representation of data, methods for working with such representations are not included in the basic I/O stream classes.

The java.io package includes a class, DataOutputStream that can be used for writing data to streams in internal, binary-number format. It provides methods for outputting all the basic Java types in machine-readable format. As with PrintStream, you can wrap any OutputStream in a DataOuputStream object. This makes it possible to write machine-readable data to that OutputStream.

For inputing such machine-readable data, java.io provides the class DataInputStream. You can wrap any InputStream in a DataInputStream object to provide it with machine-readable data-input capabilities. Data written by a DataOutputStream is guaranteed to be in a format that can be read by a DataInputStream, and vice versa. This is true even if the data stream is created on one type of computer and read on another type of computer. The cross-platform compatibility of binary data is a major aspect of Java's platform independence.

Still, the fact remains that much I/O is done in the form of ASCII text. In view of this, it is surprising that Java does not provide a standard input stream class that can read ASCII text data in a manner that is reasonably symmetrical with the ASCII output capabilities of PrintStream. Fortunately, Java's object-oriented nature makes it possible to write such a class and then use it in exactly the same way as if it were a standard part of the language.

I have written a class called AsciiInputStream that allows convenient input of data written in ASCII text format. The source code for this class is available if you want to read it. The AsciiInputStream class is a subclass of FilterInputStream, which means that you can use an AsciiInputStream as a wrapper for another input stream. The constructor

      public AsciiInputStream(InputStream dataSource)

creates an object that can be used to read data from the given InputStream, dataSource, using the convenient input methods of the AsciiInputStream class. These methods include:

       public char peek()     // Look at the next character in the stream,
                              //     without removing it from the stream.  If all
                              //     the characters in the stream have already
                              //     been read, then the character '\0' is
                              //     returned.  If the next character in the
                              //     stream is a carriage return, then a '\n'
                              //     is returned.
                              
       public void skipWhiteSpace() // Read and discard whitespace characters
                                    //   (space, carriage return, tab), until
                                    //   a non-whitespace character is seen.  

       public boolean eoln()  // Discards any spaces and tabs in the stream,
                              //     then tests whether the next char is
                              //     the end of the current line (or the
                              //     end of the data in the stream).
                              
       public boolean eof()   // Discards any whitespace characters, then
                              //     returns true if all the characters
                              //     in the stream have been read.

       public char readChar()       // These routines read values of the
       public byte readByte()       //   specified types.  In each case,
       public short readShort()     //   the computer will skip any whitespace
       public int readInt()         //   characters before trying to read a
       public long readLong()       //   value of the specified type.
       public float readFloat()     //   An error occurs if a value of the
       public double readDouble()   //   correct type is not found.  For
       public String readWord()     //   the readWord() routine, a word is
       public boolean readBoolean() //   considered to be any string of
                                    //   non-blank characters.  For readBoolean(),
                                    //   the input can be any of the strings
                                    //   "true", "false", "t", "f", "yes",
                                    //   "no", "y", "n", "1", or "0", ignoring case.
       
       public String getln();       // Reads characters up to the end of the
                                    //   current line of input.  Then reads
                                    //   and discards the carriage return character.
                                    //   Note that this routine does NOT skip
                                    //   over leading whitespace characters, and
                                    //   that the value returned might be the
                                    //   empty string.
       
       public char getlnChar();     // These routines are provided as a
       public byte getlnByte();     //   convenience.  They are equivalent
       public short getlnShort();   //   to the above routines, except that
       public int getlnInt();       //   after successfully reading a value of
       public long getlnLong();     //   the specified type, the computer 
       public float getlnFloat();   //   reads and discards any remaining
       public double getlnDouble(); //   characters on the same line of input.
       public String getlnString();
       public boolean getlnBoolean();

If you would like to use these commands with the standard input stream, you can wrap System.in in an object of type AsciiInputStream like this:

       AsciiInputStream in = new AsciiInputStream(System.in);

You could then use in.getInt() to read an integer from standard input, in.getBoolean() to read a boolean value, and so on. Similarly, you can read ASCII data from file and network connections by wrapping the input streams for those objects in AsciiInputStreams.

There remains the question of what happens when an error occurs while one of these routines is being executed. By default, the routine will throw an exception belonging to the class AsciiInputException, which I have created as a subclass of the RuntimeException class. (Recall that you don't have to use try and catch to deal with RuntimeExceptions; however, if one occurs and is not caught, it will crash your program.) However, you can turn off this behavior if you want by calling the AsciiInputStream instance method

       public void checkIO(boolean throwExceptions)

with its parameter set to false. In that case, when an error occurs during input, no exception will be thrown. Instead, the value of an internal error flag will be set, and the program will continue. If you use this option, it is your responsibility to check for errors after each input operation. You can do this with the instance method

       public boolean checkError()

This method returns true if the most recent input operation on the AsciiInputStream produced an error, and it returns false if that operation completed successfully. It is probably easier to write robust programs by catching and handling exceptions than by continually checking for possible errors. With both options available, you can experiment with both styles of exception-handling and see which one you prefer.


[ Next Section | Previous Section | Chapter Index | Main Index ]