Previous Page TOC Next Page See Page



11


Reading and Writing with Java


by Michael Girdley

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 simply are 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.



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 to deal with all the circumstances in which you will be implementing streams.

The java.io Package


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.

The Two Big Daddies


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.

InputStream


The InputStream class abstractly implements a number of methods that allow for the consuming of bytes of data. These methods follow:

reset


read

The read method simply reads a byte. The InputStream class contains several read methods:

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.



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.

OutputStream


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:

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.

So Many Streams in Java


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 Types Type 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



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 and FileOutputStream Classes


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:\isnt\it\romantic\abc.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());
}



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.



The FileInputStream and FileOutputStream classes allow only the input and output of bytes.


The ByteArrayInputStream and ByteArrayOutputStream Classes


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 ;
    while (BAIS.available > 0) {
        System.out.print(BAIS.read());
    }
} catch (Exception e) {
    System.out.print("Something went wrong sucka.");
}



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.


FilterInputStream, FilterOutputStream, and Their Children


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.



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("\usr2\1997\girdley\myfile.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.



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.


The DataInputStream and DataOutputStream Classes


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


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.

Dealing with Files


The File and RandomAccessFile classes enable you to perform comprehensive management and to use files and the local file system.

The File Class


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 Is the file name absolute? isAbsolute(); 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.

Random Access Files


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[], Reads the remaining int off, int len) bytes in a file. public int skipBytes(int n) Skips a number of bytes in a file. public native long getFilePointer() Returns the current throws IOException; location of the file pointer. public native void seek(long pos) Sets the file pointer throws IOException; to the specified absolute position public native long length()throws Returns the length of IOException; the file. public native void close() throws Closes the file. IOException;

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());

Summary


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. And finally, 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.

Previous Page Page Top TOC Next Page See Page