Input & Output - The Real Story

(for both standard I/O & for text files)



The class Keyboard.java is a robust version for the set of basic methods that you would want for keyboard input. The class hides all of these details from new users. On the other hand, in order to make the class robust, I had to limit your abilities to obtain keyboard input.  The process also gets more complicated when one has to deal with file input (and output).

To understand how Keyboard works (and, more generally, how to deal with all kinds of  input & output), you have to learn two new concepts: exceptions and general I/O (streams).

Chapter 8 of Lewis & Loftus presents the details of how one generally deals with input and output. Because exceptions are intimately connected to I/O, that chapter first discusses how exceptions work. To provide an alternate approach to the text, this web page offers you the perspective of learning general I/O first.

Still, it is impossible to be completely divorced from exceptions. In the interim, you will have to accept some additional "boilerplate". Essentially, you postpone understanding exceptions by always adding the boilerplate code "throws Exception" immediately after the argument list for every method of every class that uses I/O.

Such boilerplate works like an "inverse" of static: The modifier static "percolates" down in that if a method A is a static method, then every method that A calls must also be static. On the other hand, exceptions percolate "up": if a method B uses "throws Exception", then every method that uses B must also include "throws Exception".

Input & Output

There is actually a lot in common between keyboard input and file input in Java. Similarly, there are many similarities between console output and file output.  Any I/O source is considered to be a stream.

Streams

In most languages, when you write a computer program, you generally assume that input comes from the keyboard and that output goes to the screen (or console).   However, languages also have means to indicate that the input may come from something other than the input and/or that the output may go to something other than the screen.

The basic alternate source is a file. Other alternatives usually require more sophistication (e.g., microphone input, audio/video I/O, or random access files). These alternatives usually require greater knowledge of the required data formats. However,  getting (input) and sending (output) data are still the two primary operations..

From the perspective of a computer language, there is not much difference between  the various forms of input (keyboard, input files, or even the more esoteric devices).  The same holds true for output.  Hence, computer scientists have developed an abstract model for all input and output called a stream.  There are two basic sorts of streams: input streams and output streams.

Input streams provides data to the computer.
The computer produces data for output streams.
Java exploits the stream concept to its max; a complete explanation goes beyond the scope of this course (it involves some higher-level concepts beyond 15-100). Still, with only a few (around 25) classes, Java is able to deal with pretty much any kind of basic input or output. The "grandparents" of the stream classes are called Reader and Writer -- however, these are quite abstract (both figuratively and literally).

When dealing with text I/O only,  the number of classes needed is smaller.

In this course, we will only deal with text streams. We create objects from the two classes that are most appropriate for dealing with text I/O: these classes are BufferedReader and PrintStream.

First, basic console output, System.out is an object of the class PrintStream. You can check the Java API's description of the class PrintStream to learn about other methods that are automatically available as part of System.out.

The Keyboard class that I created uses a BufferedReader object to process the various primitive type reads.

There are other possible classes that can be used with text streams (and other people may have their own preferences) but I feel that these two classes are best because they are simplest to use (relative to what they provide) . When I see a program using alternate I/O classes, this is a flag (that other people are completing programs for you).
Although console output is automatically available (through the object System.out), there are "intermediate" stages that are required to produce the PrintStream objects from files. Similarly, for BufferedReader.  These intermediate stages represent basic input and output streams that concerning low-level forms of the data being input/output. In 15-100, we won't directly  use these intermediate forms. Think of the intermediate stages as being transitional forms between an arbitrary stream into a stream object that we want to use.

When dealing with files, the File class will also be  needed (first).

The remaining description of how to create streams in Java assumes that the class using i/o has imported the Java.io package.  Consequently, such classes should have this import statement:

import Java.io.*;
Note that none of the code that follows is robust. Many of the I/O methods throw exceptions.


About  Files:

If you want to read or write to a file, you have to connect the file to either an input stream or to an output stream. For files, this is the first step in creating the BufferedReader / PrintStream object that you want to work with.

For any kind of file, you first need to associate the (operating system) filename with a Java object of the class File. This can be achieved as follows:

File myFile;
myFile = new File("myfilename.txt");
If the file "myfilename.txt" (a case sensitive match is required) does not already exist then the File constructor does not actually try to create the file. The statement only tries to associates the object variable myFile (referring to an instance of class File) with the potential file called "myfilename.txt".
For output files, this isn't a concern, the file doesn't need to exist beforehand because it is possible that the job of the program is to create the file.  However, if you try to get input from a file that does not exist, the program will throw an exception at the first attempt to create an input stream from the file.
It is assumed that the file resides in the same folder as the program. One can specify files in alternate directories by using basic directory movement syntax.  However, the file name will usually be typed in by the user (rather than being a "hard-wired" file name). In 15-100, we will always assume that the file exists in the same folder as the program itself.

Output :

Output is generally simpler than input because the program has specified the data types. Unfortunately, exceptions are still possible with output.

Supposedly, your program can attempt to write to a file that the user has chosen to rename/remove while the program is running -- recent operating systems disallow you from attempting to do this. However, Java is intended to work with older operating systems where such a situation is still possible.  These kinds of exceptions are extraordinary cases but a robust program should be able to address such situations.
Screen Output:

There's no work here. Whenever you start a Java application, an object called System is automatically created. Although the System object is fairly complicated, one of its components is the object System.out which refers to an object (of the class PrintStream) that provides basic screen output.

File Output:

Given a File object, you want to "convert" it into a PrintStream object so that you can use the standard PrintStream methods.  Java provides a class called FileOutputStream that associates a file with a output stream object for that file.

  FileOutputStream fOS
        = new FileOutputStream (myFile);
However, this object only has low-level methods (e.g., println is unavailable). So, you'll want to create a new PrintStream object directly from a FileOutputStream object as follows
  PrintStream outFile
        = new PrintStream (fOS);
The transitions are often combined. The following works just as well to produce outFile:

  PrintStream outFile
        = new PrintStream (
            new FileOutputStream (myFile));

Now, you can use the object OutFile just like you've used System.out.

The only difference between

System.out.println("Goodbye");
and
outFile.println("Goodbye");
is that the former statement writes the word to the screen and the latter statement writes the word into a file.


Input:

Output is a lot simpler than input. When I invoke println, I know that I'm sending a string to the output stream.

However, when I'm trying to read data from an input stream, I don't know ahead of time that the format of the data will be appropriate (e.g., I'm trying to read an integer but the input provides a name). When such things occur, exceptions will be thrown. An exception will also be thrown after one has created a File object which is created for an input stream but the file doesn't already exist on disk. Creating robust input requires an understanding of exceptions.

Keyboard input:

As part of the System object, there is a System.in object.  It's class is BufferedInputStream.

 A buffer is a logical device used to store an indeterminate amount of textual information. In all modern programming languages, keyboard input is buffered so that the user can make corrections (via backspace, for example). What is typed is only sent to the program once the user hits the Enter key. For files, the advantage of buffering is that fewer disk accesses are required: a disk access is mechanical and takes up a huge amoutn of time (relative to what can be done computationally).
A BufferedInputStream object accepts any sequence of bytes whatsoever. The keystrokes that come from the keyboard are not even treated as characters. Consequently, a BufferedInputStream object is not as easy to use as BufferedReader and so we will not use System.in directly.

Instead, we transform System.in into a BufferedReader. The transformation is essentially the same for keyboard input as it will be for file input. The intermediate stage is a InputStreamReader object (which treats the input stream as a sequence of characters).

      InputStreamReader kISR =
          new InputStreamReader(System.in);

      BufferedReader kybrd =
          new BufferedReader(kISR);

File Input:

For file input, your goal is to "convert" a File object into a BufferedReader stream object.

The first step is to create an input stream (of arbitrary type).

  FileInputStream fIS =
        new FileInputStream(myFile);
The intermediate FileInputStream object needs to be converted into an input text stream (i.e., a BufferedInputStream object like System.in). This stage is needed because a file might contain information that is not necessarily text-related (e.g., an mp3).

      InputStreamReader fISR =
           new InputStreamReader(fIS);

At this point, the text input stream object should be converted into a BufferedReader object: (just as with System.in)

      BufferedReader fInput =
           new BufferedReader(fISR);

This is possible because the BufferedReader class has overloaded its constructors (one constructor has a InputStreamReader argument while another constructor has a FileInputStream argument).


Using BufferedReader:

The basic method of a BufferedReader class is readLine(). The final step in doing input from a BufferedReader class is to convert the string  into the appropriate primitive type.

Each primitive type has an associated class (sometimes called a wrapper class) that can be used to allow the primitive type to be treated as an object.  Included among the static methods of such classes are methods to convert strings into these objects (parsing methods).

In this way, we can read a primitive type. As an example, the following demonstrates what  is needed to read an int.

String tmpString = keybd.readLine();
    // uses BufferedReader to get the
    // next input value (called a token)
    // delimited by "whitespace"

int result = Integer.parseInt(tmpString);
    // parsing the string as an integer
    // might raise an exception if the
    // string doesn't conform to an
    // int format

More commonly, this is written as:
int result =
    Integer.parseInt(keybd.readLine());