Java Input and Output

The java.io package defines I/O (input/output) in terms of streams.

Streams are ordered sequences of data that have

 			a source (inpute streams) or

destination (output streams).

The package   java.io   has two major parts:

Characters are 16-bit Unicode characters, whereas bytes as always are 8 bits.

I/O is either text-based or data-based.

Text-based I/O works with streams of human-readable chracters, while data-based I/O works with streams of binary data.

The byte streams are called input streams and output streams.
The character streams are called readers and writers.
 

Byte Stream Classes


 
 

Character Stream Classes

Documentation

All these cases make up the IO 'zoo'. It contains many animals, some quite exotic.

In addition in java.util there are classes for handling ZIP files.


For nearly every input stream there is a corresponding output stream, and for most input or output streams there is a corresponding reader or writer character stream of similar functioinality, and vice versa. This `rule of thumb' can help you to orient in the variety of IO classes.

There are four categories of classes


 

The File class

Java.io.File represents the name of a file or a directory which might exist on the file system of the host machine. It encapsulates a meaning similar to FILE in C.
The File reference is used by many of the various stream classes.

Some constructors

  • File(String pathname)
  • File(String dir, String subPath)
  • File(File dir, String subPath)
  • Some calls.

    File f = new File("/tmp", "myfile); // UNIX
     
    File f1 = new File("D:\\cps530\stuff"); // stuff is a directory
    File f2 = new File(f1, "ass2.java");

    Getting File info

    There are many calls that can be made on a File object to get information about the referenced file. For example:
    exists(), canRead(), length(), etc.
    See the File class documentation.
     

    The RandomAccesFile class.

    Similar to File, this class takes advantage of the fact that a disk file is not really a stream of bytes flowing from a producer to a consumer (the stream model) but exists in its entirety, in static form, on the disk, rather like an array.

    If you reference a disk file with a RandomAccessFile reference instead of a File reference, you can use seek() to move to a desired point in the file and then access it from there. The method seek() counts in bytes and the count is from zero, just like an array index.

    For details, see the Java documentation on RandomAccessFile. (Not on the cps530 course).
     
     
     
     

    Streams.

    The stream abstraction for I/O is very powerful one. It is used in C++ as well as Java. The idea is of a flow of data from a producer object to a consumer object.

    In between there can be other objects which may filter the stream, buffer it, or divide it into structured chunks.

    The producer object can be a file, but it could also be the keyboard, an Internet connection, or anything which produces a flow of data.

    The consumer might be a Java AWT component such as a TextArea, or another file, or an Internet connection or an object representing a printer.

    Java 1.1 and later has two kinds of streams. Byte streams are flows of (binary) data. Character streams are flows of 16 bit Unicode characters.

    Character streams did not exist in Java 1.0. They were added for the purposes of internationalization. (As long as only ascii characters were used, one byte was enough to encode them so the streams for characters and bytes could be the same.)

    The on-line tutorial provides categorization of streams in terms of functionality.

    Note types of I/O related to
     

    Byte Streams

    As you can see from the tree diagram at the top of this page, the two parent classes for these kind of streams are InputStream and OutputStream. These represent flows of raw bytes of data. The data is not processed, or interpreted, in any way.

    Coverage of all the child streams is beyond the scope of this course. Here are names of only a few classes:

    Character Streams

    These streams are dedicated to text processing in a way suitable for internationalization. The two parent classes in this case are Reader and Writer.

    Only a few of the child classes need to be mentioned. These are BufferedReader, PrintWriter, OutputStreamWriter, InputStreamReader, FileReader, FileWriter. The latter two classes are actually used to convert from byte streams to character streams.

    For convenience, there still exists the PrintStream class, deprecated from Java 1.1 which will print bytes as characters. This is useful for compatibility with stdout.
     

    Reading and Writing

    All input streams implement 3 versions of read() and all output streams implement 3 versions of write(). They are inherited from Reader or Writer, or InputStream or OutputStream. The stream versions deal with 8 bit bytes, the Reader/Writer versions deal with 16 bit characters.

    There can be several forms with different arguments. All return ints. Note that InputStream, OutputStream, Reader and Writer are all abstract as are the read() and write() methods which belong to them. This means that subclasses are responsible for implementing these methods. Not all the subclasses do so.

     
     InputStream  Reader
     int read();

    int read(byte [] b)

    int read(byte [] b, int offset, int length)

     int read()

    int read(char[] c)

    int read(char[] c, int offset, int length)

     OutputSream Writer
    write(int b)

    write(int [] b)

    write(int [] b, int offset, int length)

     write(int c)

    write(char [] c)

    write(char [] c, int offset, int length)

    write(String s)

    write(String s, int offset, int length)

    All these throw IOExceptions.

    The many subclasses of these classes add more functionality by reading and writing more structured data such as lines of text or floating point numbers.

    Question: can "read" block, and if yes, when?
     

    Answer: - ?
     

    Question: Why "read()" from InputStream has the return type "int"? It is supposed to read bytes, not integers.
     

    Answer: - ?
     

     
     

    Stream Wrapping.

    The number of stream classes is overwhelming and it is no use trying to memorize them all. To use them you can look up the details.

    But there is one key idea you need to know about: how to combine streams.

    It is especially important to know how to combine the source/destination type streams with the processing type streams.

    You do this by linking them together or wrapping the processing streams around the source/destination streams. (The process is not unlike wrapping primitive data types with the corresponding Java classes. With streams thought there can be multiple layers.)

    Wrapping examples.

    The DataInputStream class allows you to read in various numerical primitive data types from a (binary) file (see the documentation for details).
    File df = new File("myNumbers.dat");
    FileInputStream fin = new FileInputStream(df);
    // now wrap a DataInputStream around the FileInputStream
    DataInputStream din = new DataInputStream(fin);
    // now access the functionality of the DataInputStream
    double d = din.readDouble();
    You could also add buffering if reading one double at a time is considered too inefficient:
    DataInputStream din = new DataInputStream (
    new BufferedInputStream (
    new FileInputStream("myNumbers.dat")));
    A processing stream is constructed on another stream (the underlying stream). The read() method in a readable filter stream reads input from the underlying stream, filters it, and passes on the filtered data to the caller.

    Similarly, the write() method in a writable filter stream filters the data and then writes it to the underlying stream.

    The processing done by the streams depends on the stream. Some streams buffer the data, some count data as it goes by, and others convert data to another form.

    In the following example we attach a filter stream to the standard input stream when we create the filter stream:

    BufferedReader d = new BufferedReader(new DataInputStream(System.in));
            String input;
    
            while ((input = d.readLine()) != null) {
                ... //do something interesting here
            }
    The readLine method has been deprecated in the DataInputStream; therefore it is wrapped in a BufferedReader.

    Using the Reader and Writer classes and their subclasses is similar:

    try {
    FileReader fr = new FileReader("myNovel.txt");
    LineNumberReader lnr = new LineNumberReader(fr);
    String s;
    int lineNum;
    while((s = lnr.readLine)) != null) {
    System.out.println(lnr.getLineNumber() + ": " + s);
    }
    lnr.close();
    fr.close()
    }
    catch (IOException e) {}
    Note the important readLine() method; see the documentation for details. LineNumberReader is a subclass of perhaps the most important Reader class, BufferedReader class.
     

    BufferedReader class

    Documentation.

    The following program demonstrates BufferedReader class and the readLine() method. The program creates a tiny text editor. It creates an array of String objects and then reads in lines of text, storing each line in the array. It will read up to 100 lines or until you enter "stop". It uses a buffer to read from the console.

    // From H.Schildt & P.Naughton "Java 2: The complete reference".
    // A tiny editor.
    import java.io.*;
    
    class TinyEdit {
      public static void main(String args[]) 
        throws IOException
      {
        // create a BufferedReader using System.in
        BufferedReader br = new BufferedReader(new 
                                InputStreamReader(System.in));
        String str[] = new String[100];
    
        System.out.println("Enter lines of text.");
        System.out.println("Enter 'stop' to quit.");
        for(int i=0; i<100; i++) {
          str[i] = br.readLine();
          if(str[i].equals("stop")) break;
        }
    
        System.out.println("\nHere is your file:");
    
        // display the lines 
        for(int i=0; i<100; i++) {
          if(str[i].equals("stop")) break;
          System.out.println(str[i]);
        }
      }
    }
    Without buffering, each invocation of read() or readLine() could cause bytes to be read from the file, converted into characters, and then returned, which can be very inefficient.
     

    Network Streams

    Java was designed to be network aware. One reason for the elaborate emphasis on streams is their applicability to networks where data arrives as streams of bytes.

    For example, streams from the Internet produced by various java.net classes are usually byte streams. Here is a simple example (later we consider the URL class; it is in java.net).

    try {
    URL url = new URL("http://www.calm.com/stuff.html");
    InputStream ins = url.getStream();
    BufferedReader bufr = new BufferedReader(
    new InputStreamReader(ins) );
    String s = bufr.readLine();
    }
    catch (MalformedURLException ex) {}
    catch (IOException e) {}

     

    PrintWriter class

    Documentation.

    For real-world programs, the recommended method of writing to the console is through a PrintWriter   stream. It is one of the character-based streams.

    This class provides methods that print formatted representations of objects to a text-output stream. This class implements all of the print methods found in PrintStream. It does not contain methods for writing raw bytes, for which a program should use unencoded byte streams. This class has 4 constructors:

    PrintWriter(OutputStream out) 
               Create a new PrintWriter, without automatic line flushing, 
                    from an existing OutputStream.
     PrintWriter(OutputStream out, boolean flushOnNewline) 
               Create a new PrintWriter from an existing OutputStream.
     PrintWriter(Writer out) 
               Create a new PrintWriter, without automatic line flushing.
     PrintWriter(Writer out, boolean autoFlush) 
               Create a new PrintWriter.
    
    
    In the second constructor, flushOnNewline controls whether Java flushes the output stream every time a newline character ('\n') is output. If it is true, then flushing automatically takes place.

    PrintWriter supports the print() and println() methods for all types including Object.

    To write to the console by using a PrintWriter, specify System.out for the output stream and flush the stream after each newline.

    The following application illustrates using a PrintWriter to handle console output:

    // Demonstrate PrintWriter
    import java.io.*;
    
    public class PrintWriterDemo {
      public static void main(String args[]) {
        PrintWriter pw = new PrintWriter(System.out, true);
        pw.println("This is a string");
        int i = -7;
        pw.println(i);
        double d = 4.5e-7;
        pw.println(d);
      }
    }
    
    
     

    Buffer Stuff

    Documentation.

    Both reading and writing is usually buffered by default.
    Here is an example of problems with the input buffer. There can also be problems with the output buffer. Sometimes you need to explicitly flush it.

    An example of a buffer problem

    This is a simple example where the intention is to read lines of text from the keyboard and write them to a file named in the program's command line.

    BufferStuff.java

    /**
     * BufferStuff.java
     * Reads lines of text from the keyboard and saves it to a file named
     * by a command line argument.
     *
     * Illustrates buffer problems. Both the input and the output are buffered.
     * This is for efficiency reasons but can cause programming problems.
     * Note the comments below and the use of  skip(), available() and the "true"
     * argument in the PrintWriter constructor.
    int available() 
        Returns the number of bytes that can be read from this file input
        stream without blocking.
    long skip(long n) 
        Skips over and discards n bytes of data from the input stream.
     *
     * DG Nov. 99
     */
    import java.io.BufferedReader;
    import java.io.PrintWriter;
    import java.io.InputStreamReader;
    import java.io.FileWriter;
    import java.io.File;
    import java.io.IOException;
    
    public class BufferStuff {
    
        public static void main(String [] args) {
    
            BufferedReader br = null;
            PrintWriter pw = null;
            File f = null;
            String line = null;
    
            if(args.length != 1) {
                 System.out.println("USAGE: java BufferStuff <filename>");
                 System.exit(0);
             }
    
            f = new File(args[0]);
            if(f.exists()) {
                 System.out.println("File Exists. Overwrite? (y/n)");
                 try {
                        switch(System.in.read()) {
    
                 // There is a buffer here. Try removing the skip()
                 // Then, if the user types 'y' an empty string is still
                // there (!) which will cause  an immediate exit of the
                // while loop below. On the other hand, if the user types
                // "yes" (still without the skip(), the 'y' will be consumed
                // and the "es" written to the file!
    
                           case 'y': System.in.skip(System.in.available())
                                      break;
                           default: System.exit(0);
                          }
                     }
                  catch (IOException e) {}
               }
    
                 try {
    
          //"true" here flushes the output buffer after every pw.println().
            // You could instead call pw.flush() after each pw.println().
            // (Actually, this is not needed in this particular program.)
    
                      pw = new PrintWriter(new FileWriter(f), true);
                      br = new BufferedReader(new InputStreamReader(System.in));
    
                      do {
                           System.out.println("Enter a line. To stop, press <enter>");
                           line = br.readLine();
                           pw.println(line);
                          }
                      while(!line.equals(""));
    
                      br.close();
                      pw.close();
                    }
    
                    catch(IOException ioe) {
                            ioe.printStackTrace();
                    }
            }
    }

     


    This program works correctly.

    Now, suppose you removed the skip() method.

    BufferStuff1a.java -BUGGED.

    In this case, strange things may happen.

    MORAL: Never forget to flush.

    Closing streams

    Don't forget to close streams. When a program exists normally the streams are closed for you. But leaving streams open when you don't need them any more can cause bugs, and misuses scarce resourcs.

    Another Example Program

    FileCopy.Java

    Illustrates a number of Java's IO classes.
     
     

    StreamTokenizer

    The class StreamTokenizer is convenient for parsing the stream (for example, a text - character stream) into tokens, allowing the tokens to be read one at a time.

    Serialization

    The on-line tutorial from Sun provides a few simple examples of working with ObjectInputStream, ObjectOutputStream. This streams are important for serialization/deserialization of objects.

    Object Serialization from the on-line tutorial.