Bruce Eckel’s Thinking in Java | Contents | Prev | Next |
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:
- New
classes have been put into the old hierarchy, so it’s obvious that Sun is
not abandoning the old streams. - 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.
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.
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.
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
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.
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.
InputStream
|
|
OutputStream
|
|
FileInputStream
|
FileReader
|
FileOutputStream
|
FileWriter
|
StringBufferInputStream
|
StringReader
|
(no
corresponding class) |
StringWriter
|
ByteArrayInputStream
|
CharArrayReader
|
ByteArrayOutputStream
|
CharArrayWriter
|
PipedInputStream
|
PipedReader
|
PipedOutputStream
|
PipedWriter
|
general, you’ll find that the interfaces in the old library components
and the new ones are similar if not identical.
Modifying
stream behavior
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.
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).
FilterInputStream
|
FilterReader
|
FilterOutputStream
|
FilterWriter
( abstract class with no subclasses) |
BufferedInputStream
|
|
BufferedOutputStream
|
BufferedWriter
|
DataInputStream
|
|
PrintStream
|
PrintWriter
|
LineNumberInputStream
|
LineNumberReader
|
StreamTokenizer
|
StreamTokenizer
(use |
PushBackInputStream
|
PushBackReader
|
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.
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
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:
DataOutputStream
|
File
|
RandomAccessFile
|
SequenceInputStream
|
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
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"); } } } ///:~
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.
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.
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.
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]
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).
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 pin"); 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()); } } ///:~
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
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:
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(); } } } ///:~
program attaches standard input to a file, and redirects standard output and
standard error to another file.
is another example in which a deprecation message is inevitable. The message
you can get when compiling with the
-deprecation
flag
is:
The constructor java.io.PrintStream(java.io.OutputStream)
has
been deprecated.
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.
Perhaps by the time you read this, the bug will be fixed.