Bruce Eckel's Thinking in Java Contents | Prev | Next

Java 1.1 IO streams

At this point you might be scratching your head, wondering if there is another design for IO streams that could require more typing. Could someone have come up with an odder design?” Prepare yourself: Java 1.1 makes some significant modifications to the IO stream library. When you see the Reader and Writer classes your first thought (like mine) might be that these were meant to replace the InputStream and OutputStream classes. But that’s not the case. Although some aspects of the original streams library are deprecated (if you use them you will receive a warning from the compiler), the old streams have been left in for backwards compatibility and:

  1. New classes have been put into the old hierarchy, so it’s obvious that Sun is not abandoning the old streams.
  2. There are times when you’re supposed to use classes in the old hierarchy in combination with classes in the new hierarchy and to accomplish this there are “bridge” classes: InputStreamReader converts an InputStream to a Reader and OutputStreamWriter converts an OutputStream to a Writer.
As a result there are situations in which you have more layers of wrapping with the new IO stream library than with the old. Again, this is a drawback of the decorator pattern – the price you pay for added flexibility.

The most important reason for adding the Reader and Writer hierarchies in Java 1.1 is for internationalization. The old IO stream hierarchy supports only 8-bit byte streams and doesn’t handle the 16-bit Unicode characters well. Since Unicode is used for internationalization (and Java’s native char is 16-bit Unicode), the Reader and Writer hierarchies were added to support Unicode in all IO operations. In addition, the new libraries are designed for faster operations than the old.

As is the practice in this book, I will attempt to provide an overview of the classes but assume that you will use online documentation to determine all the details, such as the exhaustive list of methods.

Sources and sinks of data

Almost all of the Java 1.0 IO stream classes have corresponding Java 1.1 classes to provide native Unicode manipulation. It would be easiest to say “Always use the new classes, never use the old ones,” but things are not that simple. Sometimes you are forced into using the Java 1.0 IO stream classes because of the library design; in particular, the java.util.zip libraries are new additions to the old stream library and they rely on old stream components. So the most sensible approach to take is to try to use the Reader and Writer classes whenever you can, and you’ll discover the situations when you have to drop back into the old libraries because your code won’t compile.

Here is a table that shows the correspondence between the sources and sinks of information (that is, where the data physically comes from or goes to) in the old and new libraries.

Sources & Sinks:

Java 1.0 class

Corresponding Java 1.1 class

InputStream

Reader

converter: InputStreamReader

OutputStream

Writer

converter: OutputStreamWriter

FileInputStream

FileReader

FileOutputStream

FileWriter

StringBufferInputStream

StringReader

(no corresponding class)

StringWriter

ByteArrayInputStream

CharArrayReader

ByteArrayOutputStream

CharArrayWriter

PipedInputStream

PipedReader

PipedOutputStream

PipedWriter

In general, you’ll find that the interfaces in the old library components and the new ones are similar if not identical.

Modifying stream behavior

In Java 1.0, streams were adapted for particular needs using “decorator” subclasses of FilterInputStream and FilterOutputStream. Java 1.1 IO streams continues the use of this idea, but the model of deriving all of the decorators from the same “filter” base class is not followed. This can make it a bit confusing if you’re trying to understand it by looking at the class hierarchy.

In the following table, the correspondence is a rougher approximation than in the previous table. The difference is because of the class organization: while BufferedOutputStream is a subclass of FilterOutputStream, BufferedWriter is not a subclass of FilterWriter (which, even though it is abstract, has no subclasses and so appears to have been put in either as a placeholder or simply so you wouldn’t wonder where it was). However, the interfaces to the classes are quite a close match and it’s apparent that you’re supposed to use the new versions instead of the old whenever possible (that is, except in cases where you’re forced to produce a Stream instead of a Reader or Writer).

Filters:

Java 1.0 class

Corresponding Java 1.1 class

FilterInputStream

FilterReader

FilterOutputStream

FilterWriter ( abstract class with no subclasses)

BufferedInputStream

BufferedReader

(also has readLine( ))

BufferedOutputStream

BufferedWriter

DataInputStream

use DataInputStream

(Except when you need to use readLine( ), when you should use a BufferedReader)

PrintStream

PrintWriter

LineNumberInputStream

LineNumberReader

StreamTokenizer

StreamTokenizer

(use constructor that takes a Reader instead)

PushBackInputStream

PushBackReader

There’s one direction that’s quite clear: Whenever you want to use readLine( ), you shouldn’t do it with a DataInputStream any more (this is met with a deprecation message at compile time), but instead use a BufferedReader. Other than this, DataInputStream is still a “preferred” member of the Java 1.1 IO library.

To make the transition to using a PrintWriter easier, it has constructors that take any OutputStream object. However, PrintWriter has no more support for formatting than PrintStream does; the interfaces are virtually the same.

Unchanged Classes

Apparently, the Java library designers felt that they got some of the classes right the first time so there were no changes to these and you can go on using them as they are:

Java 1.0 classes without corresponding Java 1.1 classes

DataOutputStream

File

RandomAccessFile

SequenceInputStream

The DataOutputStream, in particular, is used without change, so for storing and retrieving data in a transportable format you’re forced to stay in the InputStream and OutputStream hierarchies.

An example

To see the effect of the new classes, let’s look at the appropriate portion of the IOStreamDemo.java example modified to use the Reader and Writer classes:

//: NewIODemo.java
// Java 1.1 IO typical usage
import java.io.*;

public class NewIODemo {
  public static void main(String[] args) {
    try {
      // 1. Reading input by lines:
      BufferedReader in =
        new BufferedReader(
          new FileReader(args[0]));
      String s, s2 = new String();
      while((s = in.readLine())!= null)
        s2 += s + "\n";
      in.close();

      // 1b. Reading standard input:
      BufferedReader stdin =
        new BufferedReader(
          new InputStreamReader(System.in));      
      System.out.print("Enter a line:");
      System.out.println(stdin.readLine());

      // 2. Input from memory
      StringReader in2 = new StringReader(s2);
      int c;
      while((c = in2.read()) != -1)
        System.out.print((char)c);

      // 3. Formatted memory input
      try {
        DataInputStream in3 =
          new DataInputStream(
            // Oops: must use deprecated class:
            new StringBufferInputStream(s2));
        while(true)
          System.out.print((char)in3.readByte());
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 4. Line numbering & file output
      try {
        LineNumberReader li =
          new LineNumberReader(
            new StringReader(s2));
        BufferedReader in4 =
          new BufferedReader(li);
        PrintWriter out1 =
          new PrintWriter(
            new BufferedWriter(
              new FileWriter("IODemo.out")));
        while((s = in4.readLine()) != null )
          out1.println(
            "Line " + li.getLineNumber() + s);
        out1.close();
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 5. Storing & recovering data
      try {
        DataOutputStream out2 =
          new DataOutputStream(
            new BufferedOutputStream(
              new FileOutputStream("Data.txt")));
        out2.writeDouble(3.14159);
        out2.writeBytes("That was pi");
        out2.close();
        DataInputStream in5 =
          new DataInputStream(
            new BufferedInputStream(
              new FileInputStream("Data.txt")));
        BufferedReader in5br =
          new BufferedReader(
            new InputStreamReader(in5));
        // Must use DataInputStream for data:
        System.out.println(in5.readDouble());
        // Can now use the "proper" readLine():
        System.out.println(in5br.readLine());
      } catch(EOFException e) {
        System.out.println("End of stream");
      }

      // 6. Reading and writing random access
      // files is the same as before.
      // (not repeated here)

    } catch(FileNotFoundException e) {
      System.out.println(
        "File Not Found:" + args[1]);
    } catch(IOException e) {
      System.out.println("IO Exception");
    }
  }
} ///:~ 

In general, you’ll see that the conversion is fairly straightforward and the code looks quite similar. There are some important differences, though. First of all, since random access files have not changed, section 6 is not repeated.

Section 1 shrinks a bit because if all you’re doing is reading line input you need only to wrap a BufferedReader around a FileReader. Section 1b shows the new way to wrap System.in for reading console input, and this expands because System.in is a DataInputStream and BufferedReader needs a Reader argument, so InputStreamReader is brought in to perform the translation.

In section 2 you can see that if you have a String and want to read from it you just use a StringReader instead of a StringBufferInputStream and the rest of the code is identical.

Section 3 shows a bug in the design of the new IO stream library. If you have a String and you want to read from it, you’re not supposed to use a StringBufferInputStream any more. When you compile code involving a StringBufferInputStream constructor, you get a deprecation message telling you to not use it. Instead, you’re supposed to use a StringReader. However, if you want to do formatted memory input as in section 3, you’re forced to use a DataInputStream – there is no “DataReader” to replace it – and a DataInputStream constructor requires an InputStream argument. So you have no choice but to use the deprecated StringBufferInputStream class. The compiler will give you a deprecation message but there’s nothing you can do about it. [48]

Section 4 is a reasonably straightforward translation from the old streams to the new, with no surprises. In section 5, you’re forced to use all the old streams classes because DataOutputStream and DataInputStream require them and there are no alternatives. However, you don’t get any deprecation messages at compile time. If a stream is deprecated, typically its constructor produces a deprecation message to prevent you from using the entire class, but in the case of DataInputStream only the readLine( ) method is deprecated since you’re supposed to use a BufferedReader for readLine( ) (but a DataInputStream for all other formatted input).

If you compare section 5 with that section in IOStreamDemo.java, you’ll notice that in this version, the data is written before the text. That’s because a bug was introduced in Java 1.1, which is shown in the following code:

//: IOBug.java
// Java 1.1 (and higher?) IO Bug
import java.io.*;

public class IOBug {
  public static void main(String[] args) 
  throws Exception {
    DataOutputStream out =
      new DataOutputStream(
        new BufferedOutputStream(
          new FileOutputStream("Data.txt")));
    out.writeDouble(3.14159);
    out.writeBytes("That was the value of pi\n");
    out.writeBytes("This is pi/2:\n");
    out.writeDouble(3.14159/2);
    out.close();

    DataInputStream in =
      new DataInputStream(
        new BufferedInputStream(
          new FileInputStream("Data.txt")));
    BufferedReader inbr =
      new BufferedReader(
        new InputStreamReader(in));
    // The doubles written BEFORE the line of text
    // read back correctly:
    System.out.println(in.readDouble());
    // Read the lines of text:
    System.out.println(inbr.readLine());
    System.out.println(inbr.readLine());
    // Trying to read the doubles after the line
    // produces an end-of-file exception:
    System.out.println(in.readDouble());
  }
} ///:~ 

It appears that anything you write after a call to writeBytes( ) is not recoverable. This is a rather limiting bug, and we can hope that it will be fixed by the time you read this. You should run the above program to test it; if you don’t get an exception and the values print correctly then you’re out of the woods.

Redirecting standard IO

Java 1.1 has added methods in class System that allow you to redirect the standard input, output, and error IO streams using simple static method calls:

setIn(InputStream)

setOut(PrintStream)

setErr(PrintStream)

Redirecting output is especially useful if you suddenly start creating a large amount of output on your screen and it’s scrolling past faster than you can read it. Redirecting input is valuable for a command-line program in which you want to test a particular user-input sequence repeatedly. Here’s a simple example that shows the use of these methods:

//: Redirecting.java
// Demonstrates the use of redirection for 
// standard IO in Java 1.1
import java.io.*;

class Redirecting {
  public static void main(String[] args) {
    try {
      BufferedInputStream in = 
        new BufferedInputStream(
          new FileInputStream(
            "Redirecting.java"));
      // Produces deprecation message:
      PrintStream out =
        new PrintStream(
          new BufferedOutputStream(
            new FileOutputStream("test.out")));
      System.setIn(in);
      System.setOut(out);
      System.setErr(out);

      BufferedReader br = 
        new BufferedReader(
          new InputStreamReader(System.in));
      String s;
      while((s = br.readLine()) != null)
        System.out.println(s);
      out.close(); // Remember this!
    } catch(IOException e) {
      e.printStackTrace();
    }
  }
} ///:~ 

This program attaches standard input to a file, and redirects standard output and standard error to another file.

This is another example in which a deprecation message is inevitable. The message you can get when compiling with the -deprecation flag is:

Note: The constructor java.io.PrintStream(java.io.OutputStream)

has been deprecated.

However, both System.setOut( ) and System.setErr( ) require a PrintStream object as an argument, so you are forced to call the PrintStream constructor. You might wonder, if Java 1.1 deprecates the entire PrintStream class by deprecating the constructor, why the library designers, at the same time as they added this deprecation, also add new methods to System that required a PrintStream rather than a PrintWriter, which is the new and preferred replacement. It’s a mystery.


[48] Perhaps by the time you read this, the bug will be fixed.

Contents | Prev | Next