Like the abstract InputStream and OutputStream classes, these classes support only reading and writing at the byte
level. That is, we can only read bytes and byte arrays from the object fin.
byte b = (byte) fin.read();
As you will see in the next section, if we just had a DataInputStream, we could read numeric types:
DataInputStream din = . . .;
double s = din.readDouble();
But just as the FileInputStream has no methods to read numeric types, the DataInputStream has no method to get data
from a file.
Java uses a clever mechanism to separate two kinds of responsibilities. Some streams (such as the FileInputStream and the
input stream returned by the openStream method of the URL class) can retrieve bytes from files and other more exotic
locations. Other streams (such as the DataInputStream and the PrintWriter) can assemble bytes into more useful data
types. The Java programmer has to combine the two. For example, to be able to read numbers from a file, first create a
FileInputStream and then pass it to the constructor of a DataInputStream.
Click here to view code imageClick here to view code image
FileInputStream fin = new FileInputStream("employee.dat");
DataInputStream din = new DataInputStream(fin);
double s = din.readDouble();
If you look at Figure 1.1 again, you can see the classes FilterInputStream and FilterOutputStream. The subclasses of
these classes are used to add capabilities to raw byte streams.
You can add multiple capabilities by nesting the filters. For example, by default streams are not buffered. That is, every
call to read asks the operating system to dole out yet another byte. It is more efficient to request blocks of data instead
and store them in a buffer. If you want buffering
and
the data input methods for a file, you need to use the following rather
monstrous sequence of constructors:
DataInputStream din = new DataInputStream(
new BufferedInputStream(
new FileInputStream("employee.dat")));
Notice that we put the DataInputStream
last
in the chain of constructors because we want to use the DataInputStream
methods, and we want
them
to use the buffered read method.
Sometimes you’ll need to keep track of the intermediate streams when chaining them together. For example, when reading
input, you often need to peek at the next byte to see if it is the value that you expect. Java provides the
PushbackInputStream for this purpose.
Click here to view code imageClick here to view code image
PushbackInputStream pbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("employee.dat")));
Now you can speculatively read the next byte
int b = pbin.read();
and throw it back if it isn’t what you wanted.
if (b != '<') pbin.unread(b);
However, reading and unreading are the
only
methods that apply to a pushback input stream. If you want to look ahead and also
read numbers, then you need both a pushback input stream and a data input stream reference.
DataInputStream dindin = new DataInputStream(
pbinpbin = new PushbackInputStream(
new BufferedInputStream(
new FileInputStream("employee.dat"))));
Of course, in the stream libraries of other programming languages, niceties such as buffering and lookahead are automatically
taken care of, so it is a bit of a hassle to resort, in Java, to combining stream filters. However, the ability to mix and
match filter classes to construct truly useful sequences of streams does give you an immense amount of flexibility. For
example, you can read numbers from a compressed ZIP file by using the following sequence of streams (see Figure 1.4):
Click here to view code imageClick here to view code image
ZipInputStream zin = new ZipInputStream(new FileInputStream("employee.zip"));
DataInputStream din = new DataInputStream(zin);