Previous | Next | Trail Map | To 1.1 -- And Beyond! | Migrating to 1.1

How to Convert Code that Uses I/O

To support internationalization, the JDK 1.1 release adds character streams to the java.io package. In addition, a few of the package's classes and methods have been deprecated. Converting a 1.0 program that uses java.io API usually requires two steps:
  1. If the program uses a byte stream to read or write data, decide whether the byte stream is still appropriate for the data being read or written by the program. If not, convert the program so that it uses a character stream instead.
  2. Make sure that the program does not use any deprecated API.
The following two sections provide details and examples to help you with these two steps.

Step 1: If Appropriate, Switch from Byte Streams to Character Streams

Does the program use a byte stream (an object that inherits from InputStream or OutputStream)? If so, does the stream truly contain byte data, or does it contain characters? If the stream contains characters, then you should seriously consider changing the program to use character streams, which were introduced in 1.1.

Character streams act like byte streams except that they contain 16-bit Unicode characters rather than 8-bit bytes. With character streams, you can write programs that don't depend upon a specific character encoding, and are therefore easy to internationalize.

Another advantage of character streams is that they are potentially much more efficient than byte streams. The implementations of many of Java's original byte streams are oriented around byte-at-a-time read and write operations. The character-stream classes, in contrast, are oriented around buffer-at-a-time read and write operations. This difference, in combination with a more efficient locking scheme, allows the character stream classes to make up for the added overhead of encoding conversion in many cases.

Character streams are implemented by the java.io Reader and Writer classes and their subclasses. The character-stream classes support essentially the same operations as the byte-stream classes, except that where byte-stream methods operate on bytes or byte arrays, character-stream methods operate on characters, character arrays, or strings.

Although the Java language stores strings in Unicode, typical user-readable text files use encodings that are not necessarily related to Unicode, or even to ASCII. Character streams hide the complexity of dealing with these encodings by providing two classes that serve as bridges between byte streams and character streams. The InputStreamReader class implements a character-input stream that reads bytes from a byte-input stream and converts them to characters according to a specified encoding. Similarly, the OutputStreamWriter class implements a character-output stream that converts characters into bytes according a specified encoding and writes them to a byte-output stream.

Most of the functionality available for byte streams is also provided for character streams. This is reflected in the name of each character-stream class, whose prefix is usually shared with the name of the corresponding byte-stream class. For example, there is a PushbackReader class that provides the same functionality for character streams that is provided by PushbackInputStream for byte streams. See Character Streams versus Byte Streams for a list of the character-stream classes and their corresponding byte-stream classes.

Step 2: Remove Calls to Deprecated Classes and Methods

Of the two classes and five methods that were deprecated in java.io, only one seems to give programmers trouble: DataInputStream's readLine method. This section talks about the alternatives to this method and provides examples. For alternatives to the other classes and methods that were deprecated in java.io, consult Alternatives to Deprecated Classes and Methods in java.io.

Many programs that use the DataInputStream readLine method can be converted to use the BufferedReader's readLine method instead. Sometimes, this change is straightforward. A programmer can simply modify the program so that it creates and uses a BufferedReader instead of a DataInputStream. Code like this:

DataInputStream d = new DataInputStream(in);
changes to:
BufferedReader d = new BufferedReader(new InputStreamReader(in));
Let's look at an example from Reading Directly from a URL(in the Custom Networking trail). That section features the URLReader program that calls DataInputStream readLine. Here's the program in its original 1.0 form with the call to the deprecated API shown in bold:
import java.net.*;
import java.io.*;

class URLReader {
    public static void main(String[] args) throws Exception {
        URL yahoo = new URL("http://www.yahoo.com/");
        DataInputStream in = new DataInputStream(
                                 yahoo.openStream());

        String inputLine;

        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);

        in.close();
    }
}
To change this program so that it no longer uses deprecated API, we can modify the program to use a BufferedReader instead of a DataInputStream. Here's the new 1.1 version of this program with the changes shown in bold:
import java.net.*;
import java.io.*;

class URLReader {
    public static void main(String[] args) throws Exception {
        URL yahoo = new URL("http://www.yahoo.com/");
	BufferedReader in = new BufferedReader(
                                new InputStreamReader(
                                yahoo.openStream()));

        String inputLine;

        while ((inputLine = in.readLine()) != null)
            System.out.println(inputLine);

        in.close();
    }
}
We can make this simple change to URLReader because readLine is the only DataInputStream method used by this program.

However, DataInputStream and BufferedReader are not source code compatible (they do not support all of the same methods). If your program uses other DataInputStream methods that are not supported by BufferedReader, then your conversion may not be this simple.

For instance, one example in this book, DataIOTest from the Using DataInputStream and DataOutputStream(in the Learning the Java Language trail), uses not only readLine, but also three of DataInputStream's other readXXX methods: readDouble, readInt, and readChar. Here's the 1.0 version of DataIOTest with these method calls shown in bold:

import java.io.*;

class DataIOTest {
    public static void main(String[] args) throws IOException {

        // write the data out
        DataOutputStream out = new DataOutputStream(new
                                   FileOutputStream("invoice1.txt"));

        double[] prices = { 19.99, 9.99, 15.99, 3.99, 4.99 };
        int[] units = { 12, 8, 13, 29, 50 };
        String[] descs = { "Java T-shirt",
                           "Java Mug",
                           "Duke Juggling Dolls",
                           "Java Pin",
                           "Java Key Chain" };

        for (int i = 0; i < prices.length; i ++) {
            out.writeDouble(prices[i]);
            out.writeChar('\t');
            out.writeInt(units[i]);
            out.writeChar('\t');
            out.writeChars(descs[i]);
            out.writeChar('\n');
        }
        out.close();

        // read it in again
        DataInputStream in = new DataInputStream(new
                                 FileInputStream("invoice1.txt"));

        double price;
        int unit;
        String desc;
        double total = 0.0;

        try {
            while (true) {
                price = in.readDouble();
                in.readChar();       // throws out the tab
                unit = in.readInt();
                in.readChar();       // throws out the tab
                desc = in.readLine();
                System.out.println("You've ordered " +
                                    unit + " units of " +
                                    desc + " at $" + price);
                total = total + unit * price;
            }
        } catch (EOFException e) {
        }
        System.out.println("For a TOTAL of: $" + total);
        in.close();
    }
}
The solution of swapping a BufferedReader for the DataInputStream won't work in this program because BufferedReader does not support readDouble, readInt, or readChar.

In this situation, you have three choices:

  1. Change the algorithm so that the program doesn't need to read lines.
  2. Use an ObjectInputStream in place of the DataInputStream. (ObjectInputStream provides methods for reading all of Java's primitive data types and so supports readDouble, readInt, or readChar as well as readLine.)
  3. Call DataInputStream's readChar method iteratively.
The first option won't work for most programs. Because DataIOTest is an example of how to use DataInputStream it doesn't really make sense to use an ObjectInputStream instead. So for this example, option #2 won't work. But, this situation is a bit unusual and it's likely that ObjectInputStream will serve most programmers better (who wants to re-write readLine all the time especially given that a newline character is differs by platform?). So, we'll show you two solutions for the DataIOTest example one implementing option #2 and one implementing option #3.

Here's DataIOTest modified to use an ObjectInputStream instead of a DataInputStream. Note that the DataOutputStream had to be changed to an ObjectOutputStream so that the data written by it could be read by the ObjectInputStream.

import java.io.*;

class DataIOTest {
    public static void main(String[] args) throws IOException {

        // write the data out
        ObjectOutputStream out = new ObjectOutputStream(new
                                   FileOutputStream("invoice1.txt"));

	// ...
	// unchanged code removed for the sake of brevity
	// ...

        // read it in again
        ObjectInputStream in = new ObjectInputStream(new
                                   FileInputStream("invoice1.txt"));
	// ...
	// unchanged code removed for the sake of brevity
	// ...

    }
}
And here's another version modified to use DataInputStream's readChar method iteratively instead of readLine:
import java.io.*;

class DataIOTest {
    public static void main(String[] args) throws IOException {

	// ...
	// unchanged code removed for the sake of brevity
	// ...

        double price;
        int unit;
        StringBuffer desc;
        double total = 0.0;

        try {
            while (true) {
                price = in.readDouble();
                in.readChar();       // throws out the tab
                unit = in.readInt();
                in.readChar();       // throws out the tab
                char chr;
                desc = new StringBuffer(20);
                char lineSep = System.getProperty("line.separator").charAt(0);
                while ((chr = in.readChar()) != lineSep)
                    desc.append(chr);
                System.out.println("You've ordered " +
                                    unit + " units of " +
                                    desc + " at $" + price);
                total = total + unit * price;
            }
        } catch (EOFException e) {
        }
        System.out.println("For a TOTAL of: $" + total);
        in.close();
    }
}


Previous | Next | Trail Map | To 1.1 -- And Beyond! | Migrating to 1.1