COMP 212 Lab 09: Stream-based I/O

This tutorial is all about how to do non-GUI I/O in Java. It is essentially an out-of-class lecture.

In Java, input and output is defined in terms of an abstract concept called "stream". A stream is a sequence of data; conceptually it is like a list, but not necessarily with an end.

We use distinct types of streams for input and output.

We also distinguish streams by their contents. The java.io package provides a large number of classes to perform stream I/O. This tutorial introduces you to some commonly used classes and methods, plus the StreamTokenizer class used to parse input streams.


The InputStream and OutputStream classes

Input and output in Java of byte streams are handled through subclasses of the java.io.InputStream and java.io.OutputStream classes. These classes are abstractions that Java provides for dealing with reading and writing information sequentially to and from anything you want: e.g., a disk, a string buffer, an enumeration, or an array of bytes.

Input streams

Below is the UML diagram for a few commonly used concrete subclasses of InputStream. Note that the StreamTokenizer class shown in the diagram is not a subclass of InputStream. StreamTokenizer is a utility class used to parse the stream of input, and will be discussed below.

inputstream.png (31799 bytes)

The first step in using a concrete input stream class is to specify its source for its constructor. For instance, to read the contents of a file into a String, you create a new FileInputStream, giving the filename in the constructor, as in the following:

     import java.io.*; 

     ...
 
     String buffer = new String(); 

     // read bytes until eof 
     try 
     { 
        // FileInputStream constructor takes
        // a String, a File object, or a FileDescriptor object .
        FileInputStream infile = new FileInputStream("myfile.txt");

        for (int i = infile.read(); i != -1; i = infile.read()) { 
           buffer += (char) i;
        }

        infile.close(); 
     } 
     catch(IOException e) { 
        System.err.println("Could not read from myfile.txt"); 
     } 
     catch(FileNotFoundException e) { 
        System.err.println("myfile.txt not found"); 
     } 

The available() method returns the number of bytes that can be read from the stream. The close() method closes the link between the program and the source of the data. After you close the stream, you cannot read from it anymore until you open it up again. The reset() method ensures that the next input operation on this stream begins at the top of the file again. Several read() methods are available. Each datum is read as an int or byte; to convert it to a char, you have to cast it.

Output streams

Output streams are very similar. Below is the UML diagram for a few commonly used concrete subclasses of OutputStream.

outputstream.png (24895 bytes)


The Reader and Writer classes

Input and output for characters streams are handled through subclasses of the java.io.Reader and java.io.Writer classes. These classes are abstractions that Java provides for dealing with reading and writing character data.

Below are the UML diagrams for a few commonly used Reader and Writer classes:

reader.png (23099 bytes)

writer.png (24678 bytes)


The StreamTokenizer class

Many times when reading an input stream of characters, we need to see if what we are reading is a word or a number. This process is called "tokenizing". java.io.StreamTokenizer is a class that can do simple tokenization.

Exercise: TestStreamTokenizer.java is a sample program showing how to use it. Copy it and the file ~comp212/public_html/01-spring/labs/09/input.txt to your local directory and test it. This is similar to one piece of the RPN calculator project.


I/O and the System class

The java.lang.System class is a useful class containing useful static members, such as

     System.out.println("Hello world!");
You've seen this a lot, but what's really going on? As we've mentioned before, out is a static PrintStream field of the System class. A PrintStream is a grandchild of OutputStream in the Java class hierarchy. It has methods to print text a line at a time, rathan than a character at a time. This field is initialized when a program starts to what is known as standard output or stdout. Stdout is usually the monitor screen, but you can also send stdout to a file at runtime by redirecting it from the Unix command line. E.g., contrast
     % java MyClass
with
     % java MyClass > outputfile

There is also a System.in class, similarly initialized to the InputStream standard input or stdin. Stdin represents the keyboard, but we can similarly change System.in by redirecting the input:

     % java MyClass < inputfile

There is also a System.errPrintStream standard error or stderr. Stderr also represents the monitor screen, so what's the point? This is intended to be the output stream used for error messages, and you can redirect it independently of stdout.

     % java MyClass > outputfile
     % java MyClass >& output_and_errorfile
or even (in the bash shell)
     % java MyClass 1> outputfile 2> errorfile

NOTE: System.out and System.err are the only PrintStream objects you should use. The PrintStream is deprecated, i.e., a historical hold-over that my go away. For output character streams, you should use PrintWriter instead (as described above).

So how would you read in an arbitrary number of floating point numbers from the keyboard? Here's a sample.


Command-line arguments

With many programs, you want to be able to set options about how your program should behave at runtime. For instance, your Java compiler needs to know what files to compile, or you might have a variety of features that you want your users to have access to. (Check out the manual page for the Unix command ls. There's a vast amount of features regarding how to display your data, what to display, etc.) You can handle this in several ways, but a common technique is to use command-line arguments.

Remember how all your main functions for your programs have to have the same signature?

     public static void main(String[] argv) {...}
The array of Strings is where your program receives command-line arguments. When using the UNIX java command, everything after the class name is included in argv.

Exercise: The program Echo.java just echos back the command-line arguments to stdout. Look at it, run it, and make sure you know how to give it some command-line arguments.