Chapter 11 -- Reading and Writing with Java
Chapter 11
Reading and Writing with Java
CONTENTS
Reading and writing with Java is based on the concept of streams.
Just as a stream of water flows in one direction, starting and
ending, so does a stream of data. Streams are simply linear
paths that connect a data producer and a consumer together to
allow the serial transmission of data (one chunk after
another). Streams can connect many different things. A stream
can connect two independent processes together, for example. Or,
it can connect a class to a file. It can even connect your class
to a network.
Note: |
Remember that applets can only make network or file connections back to their originating server. Streams are useful in applets only for transmitting data between the applet source and the applet itself. You will use streams in Chapter
12, "Network Programming with Java," to enable your applets to connect back to the Web server from where they came.
|
Streams are the most powerful means of data exchange in use today.
They are perfect for Java's object-oriented nature and its multithreaded
environment. Multithreading is covered in Chapter 16,
"Multithreading with Java." If you aren't familiar with
multithreading, you should just know for now that it is basically
virtual parallel processing so that your programs can run multiple
processes simultaneously.
Java uses two types of streams: input and output. These are defined
abstractly in the InputStream
and OutputStream classes.
All the types of input and output streams descend from these two
classes, which are implemented in the java.awt.io
package. These two classes and their descendants are designed
to help you deal with all the circumstances in which you will
be implementing streams.
The java.io package is a
group of library classes that enable you to implement and use
data streams in your Java programs. These classes derive from
the abstract classes java.io.InputStream
and java.io.OutputStream.
Different subclasses of these abstract classes enable you to use
different types of streams in multiple situations. FileInputStream,
for example, is a subclass of the InputStream
abstract class.
All classes in the java.io
package generally throw IOExceptions.
These exceptions deal with input and output errors. One such class
that is a descendant of the IOException
class is the EOFException,
which is thrown when a read encounters the end of a file. You
learned about exceptions in the preceding chapter. When implementing
streams in your Java programs, you need to be sure to deal with
these exceptions when necessary.
As you learned earlier, there are two major abstract classes in
the java.io package from
which all the stream classes descend: InputStream
and OutputStream. The most
important fact about these two classes is that they are all abstractly
implemented. For this reason, to actually implement a stream,
you will not use one of these classes, but instead will use their
subclasses. InputStream and
OutputStream are simply templates
for the process of stream handling.
Each of these two classes is designed to cause a thread under
which they are running to wait until all the input requested is
available to be read or written. This comes back to the concept
of multithreading. The thread in which your stream is implemented
can be blocked by the read
and write method until its
task is done.
The InputStream class abstractly
implements a number of methods that allow for the consuming of
bytes of data. These methods follow:
read
skip
available
close
mark
reset
read
The read method simply reads
a byte. The InputStream class
contains several read methods:
abstract int read()
| Returns the next byte in the stream. |
int read(byte bytearray[])
| Returns an array of bytes read in from the stream. The integer returned is the number of bytes in the array.
|
int read(byte bytearray[],
| This returns the number of bytes read. |
int offset, int length)
| The bytes read are stored in the bytearray returned, which is of size length. The offset is the offset in the bytearray where the bytes are placed.
|
Note: |
As with all the methods in the io package, all these methods throw IOExceptions.
|
The read function returns
a value of -1 to signify the end of a stream.
skip
The skip method is used to
move past a number of bytes in a stream. It takes the following
form:
long skip (long NumBytes)
Here, NumBytes is the number
of bytes you want to skip, and the number returned is the actual
number of bytes that were skipped. This number can be less than
or equal to the value in NumBytes
if the end of the stream is reached, for example.
available
The available function returns
the amount of bytes that can be read without waiting. In other
words, it returns the number of bytes that you can have right
now, without your process having to wait around for more to be
generated. The available
function has the following declaration in the InputStream
class:
abstract int available() throws IOException
{
close
The close method closes the
input stream after you are done with it. This method frees the
resources that a stream is using and allows them to be used in
other areas. This usually is included in the finally
section of your try/catch
block performing I/O tasks (see the preceding chapter).
mark
The mark void is implemented
in only some of the stream classes. It places a marker at the
current position in the stream. This marking procedure is meant
to be used in situations in which you must read a little ahead
to figure out what a stream contains by using some kind of general
parser. Look in the library source code for the InputStream
class for a better idea of how Java's designers think you would
implement this kind of parser.
You can check to see whether the stream you are using allows the
mark method by using the
markSupported function:
boolean markSupported()
This function returns true
if you can use markers; otherwise, it returns false.
The mark void accepts an
integer parameter that sets the maximum number of bytes you will
read before resetting back to the mark with the reset
method. If you read past that number of bytes, the mark is forgotten.
reset
The reset void returns your
read position back to the place where you just marked.
The OutputStream class abstractly
defines a number of methods that enable you to produce bytes for
output. These methods are write,
flush, and close.
write
The write method in the OutputStream
class does what you would expect: It places bytes into an output
stream. There are three major forms of the write
method:
abstract void write(int b)
| Writes a byte b. It blocks your process until the byte actually is written.
|
void write(byte b[])
| Writes an array of bytes. It blocks your process until the bytes actually are written.
|
void write(byte b[],
| Writes a subarray of the byte array. |
int off, int length)
| off is the offset in the array, and the length is the number of bytes written. This method also blocks your process until the bytes actually are written.
|
Why is only one of the methods abstract
for both the read and write
methods? Well, if you look at the source of the OutputStream
and InputStream classes,
you'll see that the other methods that are not abstract
simply do some manipulation and then call the original abstract
method.
flush
The flush method flushes
the stream. It pushes out any bytes that are buffered in the stream.
close
The close method closes the
stream. It releases any of the resources associated with the stream.
The basic functions are used by the subclasses of the InputStream
and OutputStream classes
to allow the reading and writing of more complicated structures
than bytes between a variety of sources. This variety of classes
is designed to take much of the "grunt work" out of
input and output for you, the programmer.
Table 11.1 lists the multiple streams available, which are described
in this chapter.
Table 11.1. Java streams.
Stream Type | Types Handled
| Function |
BufferedInputStream
| bytes | Allows the buffered input of a stream of bytes.
|
BufferedOutputStream
| bytes | Allows the buffered output of a stream of bytes.
|
ByteArrayInputStream
| bytes | A stream in which the source is a byte array.
|
ByteArrayOutputStream
| bytes | A stream for which the destination is a byte array.
|
DataInputStream
| all | Allows the input of a stream of binary data.
|
DataOutputStream
| all | Allows the output of a stream of binary data.
|
FileInputStream
| bytes | File-specific stream input.
|
FileOutputStream
| bytes | File-specific stream output.
|
FilterInputStream
| all | Parent class for implementing filtered input streams.
|
FilterOutputStream
| all | Parent class for implementing filtered output streams.
|
InputStream
| bytes | Generic input stream class.
|
LineNumberInputStream
| all | Implements a stream from which you can find out what line you are on at any time.
|
OutputStream
| bytes | Generic output stream class.
|
PipedInputStream
| all | Allows the creation of an input pipe between one thread and a producer thread.
|
PipedOutputStream
| all | Allows the creation of an output pipe between one thread and a consumer thread.
|
PrintStream
| all | Allows the typical text printing of data.
|
PushBackInputStream
| bytes | Implements an input stream with a 1-byte pushback buffer.
|
StringBufferInputStream
| strings | Allows the buffered input of a stream of strings.
|
Tip: |
Remember that these classes are all implemented in the java.io package. To implement any of them, you need to use import java.io.* or the specific class you will be using. It is best to import only what you need to conserve resources.
|
The FileInputStream class
enables you to load information from a file located in the file
system. The FileOutputStream
class does just the opposite; it writes bytes to a file in the
local file system. Suppose that you want to create an input stream
from a file and then load the bytes one at a time and print them.
The following code shows how you can use the FileInputStream
to accomplish this:
int x;
try {
// Declare the file input stream.
InputStream fis = new FileInputStream("c:isntitromanticabc.txt");
// Read in x from the file. If
not EOF then print x out.
while ((x = fis.read())!= -1) {
System.out.print(x);
}
} catch (Exception e) {
System.out.print(e.getMessage());
}
Note: |
You do not need to open the file; it is done when the FileInputStream is constructed.
|
And there you go. There is also another function you can use with
the FileInputStream and FileOutputStream
classes: the getFD function,
which returns a file descriptor of the stream.
Note: |
The FileInputStream and FileOutputStream classes allow only the input and output of bytes.
|
These two stream types enable you to create streams to and from
arrays of bytes. The following code block reads bytes from a stream
and prints them to System.out:
byte b = new byte[100];
// Code to place numbers in b here . .
try {
// Declare the new byte array input stream.
.
InputStream BAIS = new ByteArrayInputStream(b);
while (BAIS.available > 0) {
System.out.print(BAIS.read());
}
} catch (Exception e) {
System.out.print("Something went
wrong sucka.");
}
Tip: |
You also can use System.err and System.in, in addition to System.out. They represent the default error, input, and output, respectively.
|
Caution: |
Using the reset method on the ByteArrayInput stream resets the read position to the beginning of the stream in all cases, no matter what you do with the mark method.
|
The FilterInputStream and
FilterOutputStream classes
are subclasses of the InputStream
and OutputStream classes,
respectively. They function in the same way as their parents by
making possible the existence of their children. You can implement
your own filtered streams, although they will not do much good.
The real difference is made by their children (this is discussed
in the next section).
The BufferedInputStream
and BufferedOutputStream
Classes
The BufferedInputStream and
BufferedOutputStream classes
extend the idea of the stream to include the capability to buffer
the input and output stream. In a buffered stream, the
next chunk of read or written data first is placed into a buffer
and then is made available. The next read or write, in other words,
is not done to the other end of the stream but instead to a buffer
in memory.
Why is using a buffered stream advantageous? Well, there is one
major benefit: It reduces the overall number of reads and writes
to the stream by increasing the chunks of data handled at one
time. Therefore, fewer accesses and connections between the device
generating the data and the consumer occur.
Two major constructors exist for each of these types:
BufferedInputStream(InputStream InS)
BufferedInputStream(InputStream InS, int Size)
BufferedOutputStream(OutputStream OutS)
BufferedOutputStream(OutputStream OutS, int Size)
In each case, the constructor takes another instance of a stream
and wraps the new buffered stream around it. So, your old stream
is now a buffered stream. The following code declares a buffered
file output stream:
OutputStream FilOutStr = new FileOutputStream("/usr/bin/X11/dinky.dat");
OutputStream BufOutStr = new BufferedOutputStream(FilOutStr, 1024);
The second parameter in each of the cases specifies the number
of bytes the buffer will contain.
Note: |
There is also another class available that enables you to buffer an input of strings: the StringBufferInputStream. You also can use this class to do the same thing you do with bytes in the BufferedInputStream except with Strings.
|
The PushBackInputStream
Class
The PushBackInputStream class
implements one more method: unread.
This method enables you to implement a 1-byte pushback buffer.
In other words, it enables you to push a byte back onto the stream.
It implements the new unread
method along with the other methods available in the InputStream
class.
The unread method takes one
parameter: a character (or byte) that you can push back onto the
stream.
Why would you want to implement this? Suppose that you are using
the first character of a stream to specify for what segment of
your program this stream will be used. Each part of your program
that might deal with the stream then checks the first character
and, if it isn't what it is looking for, puts the character back
on the stream and passes it to the next handler.
The PrintStream
Class
Remember all those times you used the System.out.print
statement? Well, when you did, you were using one of the methods
of a class that extends the PrintStream
class.
The PrintStream class enables
you to easily handle the output of the normal Java language types,
such as integer, strings, and so on. You use this class to output
the normal print, println,
and write methods you are
accustomed to in other languages such as C++.
The print method of an instance
of the PrintStream is overloaded
to accept all the general Java language types. Suppose that you
want to write a long integer.
You can use this code:
PS.print(ALong);
The println method prints
its parameter and then moves to the next line:
PS.println(AnInteger);
You also can use the write,
flush, and close
methods. The write method
enables you to write bytes to the stream in the same format as
the original abstract OutputStream
class did.
You also can send an object to be printed. The value printed is
what results from the object's toString
function.
The LineNumberInputStream
Class
The LineNumberInputStream
class enables you to implement a stream that lets you know what
line you currently are viewing. You can declare this stream type
as a wrapper to another stream:
InputStream LnNoInpStr = new LineNumberInputStream(new
FileInputStream("usr21997girdleymyfile.txt"));
Then, at any point, you can use this code:
LnNoInpStr.getLineNumber();
This function returns an integer value of what line you are at
in the file. Also available is setLineNumber(int
No), which enables you to specify the line number
of your position. This is useful if you are looking at a file
with a header and you want to start counting line numbers after
it, for example.
Note: |
Java uses two types of character types: the ASCII format and the Unicode format. The ASCII characters are a subset of the Unicode character set. The other major difference is in the byte representation of the two types. The ASCII format is stored in seven
bits. The Unicode system stores characters in anywhere from 1 byte (8 bits) to 3 bytes for complete Unicode characters. In Java, all characters are stored in the Unicode format.
|
These two classes allow you to read and write data in a binary
format without having to worry about all the grunt work involved
in implementing and managing that data. These two classes allow
you to implement RandomAccessFileStreams,
which is covered next. The files used by these two classes are
by far the most efficient means of dealing with data in Java.
You can use the methods of the two classes to read and write Java
language types easily to and from a binary storage format.
The DataOutputStream class
enables you to use a number of methods: writeInt,
writeChar, and so on. There
is one of these methods for each of the general Java language
types. See the pattern?
The same is true for the
DataInputStream class. A
number of methods are available, including readLong,
readChar, and so on. In both
classes, you still can read and write individual bytes.
The following code loads the data in a file of integers stored
in binary format and then outputs it to the standard output:
InputStream IS = new DataInputStream(
new FileInputStream("/usr2/sun/yidata.dat"));
try {
while (true) {
System.out.print(IS.readInt());
}
}
finally {
IS.close();
}
Easy enough.
The PipedInputStream and
PipedOutputStream classes
are useful for creating pipes (a feature that should be familiar
to UNIX system users) that are used to connect two parallel threads
(see the next chapter for more information on multithreading).
To use this class, simply declare a PipedInputStream
in one process and a PipedOutputStream
in the other, and then connect them like this:
PipedInputStream InStream = new PipedInputStream();
PipedOutputStream OutStream = new PipedOutputStream(InStream);
Now, when one process outputs, the other process can access the
data.
The File and RandomAccessFile
classes enable you to perform comprehensive management and to
use files and the local file system.
The File class enables you
to construct an object that contains information about an entry
in the file system. Three constructors are available:
File(String thePath)
File(String thePath, String theFileName)
File(File dir, String Name)
Table 11.2 summarizes the methods available to you.
Table 11.2. File class methods.
Method | Description
|
public String getName()
| Returns the name of the file. |
public String getPath()
| Returns the path of the file. |
public String getAbsolutePath()
| Returns the absolute path of the file. |
public String getParent()
| Gets the name of the parent directory. |
public boolean exists()
| Does the file exist? |
public boolean canWrite()
| Can we write to the file? |
public boolean canRead()
| Can we read the file? |
public boolean isFile()
| Does a normal file exist? |
public boolean isDirectory()
| Does this directory exist? |
public native boolean isAbsolute();
| Is the file name absolute? |
public long lastModified()
| Returns the last modified date. Should only be used as a comparison to a previous change.
|
public long length()
| Returns the length of the file in bytes. |
The DataInputStream and DataOutputStream
classes allow the implementation of random access files, which
are files that you can read from and write to at any point you
specify. There are two constructors for the RandomAccessFile
class:
RandomAccessFile(String FileName, String
FileMode)
RandomAccessFile(File theFile, String FileMode)
The first parameter in each of these constructors specifies the
file with which you are dealing. The second parameter is r,
w, or rw,
which sets the mode of the file to be read, write, or read-write,
respectively.
Table 11.3 summarizes some of the methods available in the RandomAccessFile
class.
Table 11.3. RandomAccessFile
class methods.
Description | Method
|
public final FileDescriptor getFD()
| Returns the opaque file descriptor object.
|
public final void readFully(byte b[], int off, int len)
| Reads the remaining bytes in a file. |
public int skipBytes(int n)
| Skips a number of bytes in a file. |
public native long getFilePointer() throws IOException;
| Returns the current location of the file pointer.
|
public native void seek(long pos) throws IOException;
| Sets the file pointer to the specified absolute position.
|
public native long length()throws IOException;
| Returns the length of the file. |
public native void close() throws IOException;
| Closes the file. |
The normal read and write
methods also are implemented.
The following code block opens a file for reading and then prints
a character at byte position 1000 in the file:
RandomAccessFile RAF = new RandomAccessFile("HalBialeck.dat",
"r");
RAF.seek(1000);
System.out.print(RAF.read());
This chapter covered the java.io
package. As you saw, there are two "Big Daddies" in
this package. You learned about each of the standard methods available
in each of those classes. Next, you saw how the library classes
that descend from those two major classes function to allow easy
implementation of different stream types in Java. You learned
about the special details of operating on files.
Chapter 12, "Network Programming
with Java," will cover the techniques of creating applets
to utilize network resources. Chapter 12
will make a strong use of the information in this chapter to implement
streams across network connections.
Next
Previous
Contents
|