Chapter 32
java.io

by Mark Wutka

The java.io package provides different input and output streams for reading and writing data. It also provides access to common filesystem functions like file and directory creation, removal and renaming, as well as directory listing. The input and output streams can be connected to files, network sockets, or internal memory buffers.

The java.io package also contains a number of stream filters that allow you to access stream data in a variety of different formats. You can also create your own filters to add additional functionality.

Basic Stream Methods

Almost all I/O in Java is performed using streams. Think of a stream of bytes as a stream of water. When you get water from the stream, you are using it as an input stream. When you dump water into the stream, you are using it as an output stream. You can connect one Java stream to another in the same way you might connect two water hoses together.

The InputStream Class

An input stream is a source of data. All input stream classes provide methods to get data from the stream. The basic method for getting data from any InputStream object is the read method.

public abstract int read() throws IOException

reads a single byte from the input stream and returns it. This method performs a blocking read, which means that it waits for data if there is none available. This is usually applicable when the stream is on a network and the next byte of data may not have arrived yet. When the stream reaches the end of a file this method returns -1.

public int read(byte[] bytes) throws IOException

fills an array with bytes read from the stream and returns the number of bytes read. It is possible for this method to read fewer bytes than the array can hold, because there may not be enough bytes in the stream to fill it. When the stream reaches end of file this method returns -1. You will always receive all the bytes in the stream before you hit end of file. In other words, if there are 50 bytes left in the stream and you ask to read 100 bytes, this method returns the 50 bytes, then the next time it is called it returns -1.

public int read(byte[] bytes, int offset, int length)     
throws IOException

fills an array starting at position offset with up to length bytes from the stream. It returns either the number of bytes read or -1 for end of file.

The read method always blocks (it sits and waits without returning) when there is no data available. To avoid blocking, you might need to ask ahead of time exactly how many bytes you can safely read without blocking. The available method returns this number:

public int available() throws IOException

You can skip over data in a stream by passing the skip method the number of bytes you want to skip over:

public long skip(long n)

The skip method actually uses the read method to skip over bytes, so it will block under the same circumstances as read. It returns the number of bytes it skipped or -1 if it hits the end of file.

Some input streams have the notion of a bookmark where you can mark a place in the stream and return to it. The markSupported method returns true if the stream supports marking:

public boolean markSupported()

The mark method marks the current position in the stream so you can back up to it later:

public synchronized void mark(int readLimit)

The readLimit parameter sets the maximum number of bytes that can be read from the stream before the mark is no longer set. In other words, you must tell the stream how many bytes it should let you read before it forgets about the mark. Some streams may need to allocate memory to support marking and this parameter tells them how big to make their array.

If you have set a mark, you can reposition the stream back to the mark by calling the reset method:

public synchronized void reset() throws IOException

After you are done with a stream you should close it down using the close method:

public void close() throws IOException

Most streams get closed automatically at garbage collection time. On most operating systems, however, the number of files you can have open at one time is limited, so you should close your streams when you are finished with them to free up system resources immediately without waiting for garbage collection.

The OutputStream Class

Rather than being a source of data like the input stream, an output stream is a recipient of data. The most basic method of an OutputStream object is the write method.

public abstract void write(int b) throws IOException

writes a single byte of data to an output stream.

public void write(byte[] bytes) throws IOException

writes the entire contents of the bytes array to the output stream.

public void write(byte[] bytes, int offset, int length)     
throws IOException

writes length bytes from the bytes array, starting at position offset.

Depending on the type of stream, you may need to occasionally flush the stream if you need to be sure that the data written on the stream has been delivered. Flushing a stream does not destroy any information in the stream, it just makes sure that any data that is stored in internal buffers is written out onto whatever device the stream may be connected to. To flush an output stream, just call the flush method:

public void flush() throws IOException

As with the input streams, you should close output streams when you are done with them by calling the close method:

public void close() throws IOException

Filtered Streams

One of the most powerful aspects of streams is that you can chain one stream to the end of another. For example, the basic input stream only provides a read method for reading bytes. If you want to read strings and integers, you can attach a special data input stream to an input stream and suddenly have methods for reading strings, integers, and even floats. The FilterInputStream and FilterOutputStream classes provide the capability to chain streams together. They don't add any new methods, however. Their big contribution is that they are "connected" to another stream. The constructors for the FilterInputStream and FilterOutputStream take InputStream and OutputStream objects as parameters:

public FilterInputStream(InputStream in)
public FilterOutputStream(OutputStream out)

Because these classes are themselves instances of InputStream and OutputStream, they can be used as parameters to constructors to other filters, allowing you to create long chains of input and output filters.

The DataInputStream class, discussed later in this chapter, is a very useful filter that allows you to read strings, integers, and other simple types from an input stream. In addition, the LineNumberInputStream filter automatically counts lines as you read input. You can chain these filters together to read data while counting the lines:

LineNumberInputStream lineCount = new LineNumberInputStream(    
System.in);
DataInputStream dataIn = new DataInputStream(lineCount);

The PrintStream Class

You have probably already used a PrintStream object without knowing it. The System.out stream is an instance of PrintStream. The PrintStream class allows you to write printable versions of various objects to an output stream. When you create a PrintStream object, you must attach it to an existing output stream because it is a FilterOutputStream. You can optionally pass an auto-flush parameter that, if true, will cause the stream to automatically call the flush method every time it prints a new line:

public PrintStream(OutputStream out)
public PrintStream(OutputStream out, boolean autoFlush)

The PrintStream class provides the following methods for printing objects:

public synchronized void print(Object ob)
public synchronized void print(String s)
public synchronized void print(char[] s)
public synchronized void print(boolean b)
public synchronized void print(char c)
public synchronized void print(int i)
public synchronized void print(long l)
public synchronized void print(float f)
public synchronized void print(double d)
public synchronized void println(Object ob)
public synchronized void println(String s)
public synchronized void println(char[] s)
public synchronized void println(boolean b)
public synchronized void println(char c)
public synchronized void println(int i)
public synchronized void println(long l)
public synchronized void println(float f)
public synchronized void println(double d)

The only difference between the print and println methods is that the println method always appends a newline to whatever it prints, while print remains on the same line. If you need to print a newline, you can call the println method with no parameters:

public void println()


NOTE: The PrintStream class is being phased out in favor of the PrintWriter class, which contains the same methods as PrintStream, but also supports the new Writer class. The autoflush option in the PrintWriter will only flush automatically during a println call, and not whenever a newline character is printed, as the PrintStream class does.


Buffered Streams

Buffered streams help speed up your programs by reducing the number of reads and writes on system resources. Suppose you have a program that is writing one byte at a time. You may not want each write call to go out to the operating system, especially if it is writing to a disk. Instead, you would like the bytes to be accumulated into big blocks and written out in bulk. The BufferedInputStream and BufferedOutputStream classes provide this functionality. When you create them, you can provide a buffer size:

public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int bufferSize)

The BufferedInputStream class tries to read as much data into its buffer as possible in a single read call, while the BufferedOutputStream class only calls the write method when its buffer fills up, or when flush is called.

Data Streams

The DataInputStream and DataOutputStream filters are two of the most useful filters in the java.io package. They allow you to read and write Java primitive types in a machine-independent fashion. This is important if you want to write data to a file on one machine and read it in on another machine with a different CPU architecture. For example, one of the most common difficulties in transferring binary data between an Intel-based PC and a Sparc-based workstation is the different way the CPUs store integers. A number that is stored on a Sun as 0x12345678 would be interpreted by a PC as 0x78563412. Fortunately, the DataInputStream and DataOutputStream classes take care of any necessary conversions automatically.

The DataInput Interface

The DataInputStream implements a DataInput interface. This interface defines methods for reading Java primitive data types, as well as a few other methods.

The DataInput methods for reading primitive data types are:

public boolean readBoolean() throws IOException, EOFException
public byte readByte() throws IOException EOFException
public char readChar() throws IOException, EOFException
public short readShort() throws IOException, EOFException
public int readInt() throws IOException, EOFException
public long readLong() throws IOException, EOFException
public float readFloat() throws IOException, EOFException
public double readDouble() throws IOException, EOFException

Sometimes you may need to read an unsigned byte or short (16-bit integer). You can use the readUnsignedByte and readUnsignedShort to do that:

public int readUnsignedByte() throws IOException, EOFException
public int readUnsignedShort() throws IOException, EOFException

You might expect the DataInput interface to include a readString method. It does, but it isn't called readString. Instead, the method to read a string is called readUTF. UTF stands for Unicode Transmission Format and is a special format for encoding 16-bit Unicode values. UTF assumes that most of the time the upper 8 bits of a Unicode value will be 0 and optimizes with that in mind. The definition of readUTF is:

public String readUTF() throws IOException

Many times you want to read data from a text file, often one line at a time. The readLine method reads in a line from a text file terminated by \r, \n, or end of file, stripping off the \r or \n before returning the line as a string:

public String readLine() throws IOException, EOFException

When you are trying to read a fixed number of bytes into an array using the standard read method in the InputStream class, you may have to call read several times because it may return before reading the full numbers of bytes you wanted. This is especially true when you are transferring data over the network. The readFully methods explicitly wait for the full number of bytes you have requested:

public void readFully(byte[] bytes)
     throws IOException, EOFException
public void readFully(byte[] bytes, int offset, int length)

     throws IOException, EOFException

Notice that the readFully methods do not return a number of bytes as the standard read methods do. This is because you should already know how many bytes will be read, either by the size of the array in the first version of the method or the length parameter in the second. The skipBytes method performs a function similar to the readFully method; that is, it waits until the desired number of bytes have been skipped before returning. In fact, it is better to think of the method as being called "skipBytesFully".

public int skipBytes(int numBytes)

The DataOutput Interface

The DataOutput interface defines the output methods that correspond to the input methods defined in the DataInput interface. The methods defined by this interface are:

public void writeBoolean(boolean b) throws IOException
public void writeByte(int b) throws IOException
public void writeChar(int c) throws IOException
public void writeShort(int c) throws IOException
public void writeInt(int i) throws IOException
public void writeLong(long l) throws IOException
public void writeFloat(float f) throws IOException
public void writeDouble(double d) throws IOException
public void writeUTF(String s) throws IOException

You can also write a string as a series of bytes or chars using the writeBytes and writeChars methods:

public void writeBytes(String s) throws IOException
public void writeChars(String s) throws IOException

The DataInputStream and DataOutputStream Classes

The DataInputStream and DataOutputStream classes are simply stream filters that implement the DataInput and DataOutput interfaces. Their constructors are typical stream filter constructors in that they simply take the stream to filter as an argument:

public DataInputStream(InputStream in)
public DataOutputStream(OutputStream out)

Byte Array Streams

You don't always have to write to a file or the network to use streams. You can write to and read from arrays of bytes using the ByteArrayInputStream and ByteArrayOutputStream classes. These streams are not filter streams like some of the others, they are input and output streams.

When you create a ByteArrayInputStream, you must supply an array of bytes that will serve as the source of the bytes to be read from the stream.

public ByteArrayInputStream(byte[] bytes)

creates a byte input stream using the entire contents of bytes as the data in the stream.

public ByteArrayInputStream(byte[] bytes, int offset, int length)

creates a byte input stream that reads up to length bytes starting at position offset.

A ByteArrayOutputStream is an array of bytes that continually grows to fit the data that is stored in it. The constructor for the ByteArrayOutputStream class takes an optional initial size parameter that determines the initial size of the array that stores the bytes written to the stream:

public ByteArrayOutputStream()
public ByteArrayOutputStream(int initialSize)

After you have written data to a ByteArrayOutputStream, you can convert the contents of the stream to an array of bytes by calling toByteArray:

public synchronized byte[] toByteArray()

The size method returns the total number of bytes written to the stream so far:

public int size()

Char Array Streams

One of the differences between Java and languages like C is that Java treats characters as 16-bit values instead of 8-bit values. Unfortunately, under Java 1.0, there was no 16-bit version of the byte array streams. This encouraged programmers to treat characters as 8-bit values if they wanted to use the byte array streams. Fortunately, Java 1.1 includes character array streams, but they are not called streams. Java 1.1 includes a new type of stream called a "Reader" or a "Writer," depending on whether it is an input stream or an output stream. The character version of the ByteArrayInputStream is called a CharArrayReader, while the CharArrayWriter performs functions similar to the ByteArrayOutputStream.

The CharArrayReader and CharArrayWriter classes function almost identically to their byte array counterparts. They contain the same methods, only the char array streams use char values everywhere the byte array streams use byte values. For example, the constructors for CharArrayReader and CharArrayWriter look like this:

public CharArrayReader(char[] buf)
public CharArrayReader(char[] buf, int offset, int length)
public CharArrayWriter()
public CharArrayWriter(int size)

Conversion Between Bytes and Characters

Since many systems have previously treated characters as 8-bit values, you often encounter situations where you need to convert an 8-bit value into a 16-bit Java character. Java characters are 16-bits in order to support the Unicode standard, which supports a wide variety of international character sets. Currently, many programmers just take the 8-bit value and assume the upper 8 bits are 0. Unfortunately, that won't work in all situations. Java 1.1 adds a standard mechanism for converting bytes into characters and characters into bytes. That doesn't mean there is a standard conversion, however. You still have to figure out what kind of conversion you should perform. Once you figure that out, there is a standard way for you to tell an I/O stream how to convert characters.

The Reader and Writer classes represent a special category of input and output streams geared towards character operations. These classes perform an automatic conversion between bytes and characters. For each of the core input and output streams there are Reader and Writer versions for doing character operations. The following Reader and Writer classes function identically to their stream counterparts, except that they take a Reader instead of an InputStream, or a Writer instead of an OutputStream: BufferedReader, BufferedWriter, FileReader, FileWriter, FilterReader, FilterWriter, PipedReader, PipedWriter, PrinterWriter, and PushbackReader.

The InputStreamReader and OutputStreamWriter classes provide a bridge between streams and the Reader/Writer classes. InputStreamReader takes an InputStream and provides a Reader interface for it. The constructor takes an optional encoding name, which is the name of the character encoding used to translate bytes into characters:

public InputStreamReader(InputStream in)
public InputStreamReader(InputStream in, String encoding)

Likewise, the OutputStreamWriter class provides a Writer interface for an OutputStream, and also can take an optional encoding parameter:

public OutputStreamWriter(OutputStream out)
public OutputStreamWriter(OutputStream out, String encoding)

The Reader and Writer classes allow you to support international character sets in your programs in a seamless manner. If your programs are reading and writing data that could potentially be in another language, you should use the Reader/Writer objects instead of streams.

The StringBufferInputStream

The StringBufferInputStream is a close cousin to the ByteArrayInputStream. The only difference between the two is that the StringBufferInputStream constructor takes a string as the source of the stream's characters instead of a byte array:

public StringBufferInputStream(String str)

Pipe Streams

The PipedInputStream and PipedOutputStream classes allow you to hook an input stream to an output stream directly. Normally when you deal with streams, the source or the destination of the data is an external file or network. When you use pipes, your program is both the source and destination of the data. In other words, the pipes are used for creating a stream that your program both reads from and writes to. This can be useful for doing inter-thread communications, or for testing both ends of a networking protocol in a single program.

When you create a PipedInputStream or PipedOutputStream you can specify the stream to which it is connected. If you do not specify a stream in the constructor, the stream is unconnected and must be connected to another stream before it can be used. The constructors for PipedInputStream and PipedOutputStream are:

public PipedInputStream()
public PipedInputStream(PipedOutputStream outStream)
public PipedOutputStream()
public PipedOutputStream(PipedInputStream inStream)

The connect method in the PipedInputStream attaches the stream to an output stream:

public void connect(PipedOutputStream outStream) throws IOException

Likewise, the connect method in PipedOutputStream attaches the stream to an input stream:

public void connect(PipedInputStream inStream) throws IOException

Listing 32.1 shows a class that reads data strings from a pipe and prints them out.

Listing 32.1Source Code for PipeReader.java

import java.io.*;
// This class uses a DataInputStream filter attached
// to a PipedInputStream to read strings and print them.
public class PipeReader extends Object implements Runnable
{
    DataInputStream inStream;

    public PipeReader(PipedInputStream inStream)
    throws IOException
    {

// Create a DataInputStream attached to the pipe input
        this.inStream = new DataInputStream(inStream);

    }

    public void run()
    {
        try {
// Read in the total number of strings sent
            int stringCount = inStream.readInt();            
for (int i=0; i < stringCount; i++) {
// Try to read in a string
                String inString = inStream.readUTF();

// readUTF returns null with you hit end-of-file
                if (inString == null) {
                    System.out.println("EOF");
                    return;
                }
                System.out.println(inString);
            }

// Print out the string's value
        } catch (Exception oops) {
            oops.printStackTrace();
// If you get any kind of error, just quit
            return;
        }
    }
}

Listing 32.2 shows a test program that sends some strings to a PipeReader using an output pipe.

Listing 32.2Source Code for PipeTest.java

import java.io.*;

// This class creates both ends of a pipe and runs
// a PipeReader to get data off the pipe. Then it
// sends a few test strings through the pipe.

public class PipeTest extends Object
{
    public static void main(String[] args)
    {
        try {

// Create both ends of the pipe. Once you create one end, you create
// the other end by passing it the first end. In other words, once you
// create an output pipe, you create the input pipe by passing it
// the output pipe. It doesn't matter which one you create first.

            PipedOutputStream outPipe = new PipedOutputStream();
            PipedInputStream inPipe = new PipedInputStream(outPipe);

// Attach a DataOutput filter to the pipe. This makes it a snap to 
// send strings. Besides, that's what the PipeReader is expecting.

            DataOutputStream outStream =
                new DataOutputStream(outPipe);

// Create a PipeReader and run it in a separate thread.

            Thread readerThread = new Thread(
                new PipeReader(inPipe));
            readerThread.start();

// Tell the reader how many strings you are going to send it
            outStream.writeInt(4);

// Send some strings over the pipe

            outStream.writeUTF("Hello there!");
            outStream.writeUTF("This is a test");
            outStream.writeUTF("that uses pipes");
            outStream.writeUTF("to pass data to another thread");

        } catch (Exception err) {

// If there's any error, just print out a stack trace
            err.printStackTrace();
        }

    }

Object Streams

When Sun added Remote Method Invocation(RMI) to Java, it also added the capability to stream arbitrary objects. The ObjectInput and ObjectOutput interfaces define methods for reading and writing any object, in the same way that DataInput and DataOutput define methods for reading and writing primitive types. In fact, the ObjectInput and ObjectOutput interfaces extend the DataInput and DataOutput interfaces. The ObjectInput interface adds a single input method:

public abstract Object readObject()
throws ClassNotFoundException, IOException

Similarly, the ObjectOutput interface adds a single output method:

public abstract void writeObject(Object obj)
throws IOException

The ObjectOutputStream implements a stream filter that allows you to write any object to a stream, as well as any primitive type. Like most stream filters, you create an ObjectOutputStream by passing it an OutputStream:

public OutputStream(OutputStream outStream)

You can use the writeObject method to write any object to the stream:

public final void writeObject(Object ob)
throws ClassMismatchException, MethodMissingException, IOException

Because the ObjectOutputStream is a subclass of DataOutputStream, you can also use any of the methods from the DataOutput interface, such as writeInt or writeUTF.

Listing 32.3 shows a program that uses writeObject to stream a date and hash table to a file.

Listing 32.3Source Code for WriteObject.java

import java.io.*;
import java.util.*;

// This class writes out a date object and a hash table object
// to a file called "writeme" using an ObjectOutputStream.

public class WriteObject extends Object
{
     public static void main(String[] args)
     {

// Create a hash table with a few entries

          Hashtable writeHash = new Hashtable();
          writeHash.put("Leader", "Moe");
          writeHash.put("Lieutenant", "Larry");
          writeHash.put("Stooge", "Curly");

          try {

// Create an output stream to a file called "writeme"
               FileOutputStream fileOut =
                    new FileOutputStream("writeme");

// Open an output stream filter on the file stream
               ObjectOutputStream objOut =
                         new ObjectOutputStream(fileOut);

// Write out the current date and the hash table
               objOut.writeObject(new Date());
               objOut.writeObject(writeHash);

// Close the stream
               objOut.close();

          } catch (Exception writeErr) {

// Dump out any error information
               writeErr.printStackTrace();
          }
     }
}

The ObjectInputStream, as you might have guessed, implements a stream filter for the ObjectInput interface. You create an ObjectInputStream by passing it the input stream you want it to filter:

public ObjectInputStream(InputStream inStream)

The readObject method reads an object from the input stream:

public final Object readObject()
throws MethodMissingException, ClassMismatchException
     ClassNotFoundException, StreamCorruptedException, IOException

You can also use any of the methods from the DataInput interface on an ObjectInputStream.

Listing 32.4 shows a program that uses readObject to read the objects written to the "writeme" file by the example in listing 32.3.

Listing 32.4Source Code for ReadObject.java

import java.io.*;
import java.util.*;

// This class opens up the file "writeme" and reads two
// objects from it. It makes no assumptions about the
// types of the objects, it just prints them out.

public class ReadObject extends Object
{
     public static void main(String[] args)
     {
          try {

// Open an input stream to the file "writeme"
               FileInputStream fileIn =
                    new FileInputStream("writeme");

// Create an ObjectInput filter on the stream
               ObjectInputStream objIn =
                         new ObjectInputStream(fileIn);

// Read in the first object and print it
               Object ob1 = objIn.readObject();
               System.out.println(ob1);

// Read in the second object and print it
               Object ob2 = objIn.readObject();
               System.out.println(ob2);

// Close the stream
               objIn.close();

          } catch (Exception writeErr) {
// Dump any errors
               writeErr.printStackTrace();
          }
     }
}

If you do not have the latest version of Java, you can download the object serialization extensions from www.javasoft.com.

Other Streams

Java also provides a number of utility filters. These filters are special in that they do not exist in pairs--that is, they work exclusively for either input or output.

The LineNumberInputStream Class

The LineNumberInputStream allows you to track the current line number of an input stream. As usual, you create a LineNumberInputStream by passing it the input stream you want it to filter:

public LineNumberInputStream(InputStream inStream)

The getLineNumber method returns the current line number in the input stream:

public int getLineNumber()

By default, the lines are numbered starting at 0. The line number is incremented every time an entire line has been read. You can set the current line number with the setLineNumber method:

public void setLineNumber(int newLineNumber)

Listing 32.5 shows a program that prints the contents of standard input along with the current line number.

Listing 32.5Source Code for PrintLines.java

import java.io.*;

// This class reads lines from standard input (System.in) and
// prints each line along with its line number.

public class PrintLines extends Object
{
     public static void main(String[] args)
     {

// Set up a line number input filter to count the line numbers
          LineNumberInputStream lineCounter =
               new LineNumberInputStream(System.in);

// Set up a data input filter on the input stream to read in whole
// lines. Note that this stream is chained to the lineCounter stream
// rather than to System.in. If this were connected directly to System.in,
// the line counter wouldn't work.

          DataInputStream inStream = new DataInputStream(lineCounter);

          try {
               while (true) {

// Read in the next line
                    String nextLine = inStream.readLine();

// If readLine returns null, we've hit the end of the file
                    if (nextLine == null) break;

// Print out the current line number followed by the line
                    System.out.print(
                         lineCounter.getLineNumber());
                    System.out.print(": ");
                    System.out.println(nextLine);
               }
          } catch (Exception done) {
               done.printStackTrace();
          }
     }
}

The SequenceInputStream Class

The SequenceInputStream filter allows you to treat a number of input streams as one big input stream. This is useful if you want to read from a number of data files but you don't really care where one file stops and another starts. If you have a situation where you can just as easily combine all your input files into one big file, this stream will probably be of some use. You can create a SequenceInputStream that combines two streams by passing both streams to the constructor:

public SequenceInputStream(InputStream stream1, InputStream stream2)

If you want more than two streams, you can pass an enumeration to the constructor:

public SequenceInputStream(Enumeration e)

The enumeration should return the input stream objects you want to combine. A simple way to implement this is to stick all your input streams in a vector and use the vector's elements method:

Vector v = new Vector();
v.addElement(stream1);
v.addElement(stream2);
v.addElement(stream3);
v.addElement(stream4); // and so on_
InputStream seq = new SequenceInputStream(v.elements());

If you want to combine three streams, you can also create a chain of SequenceInputStreams this way:

InputStream seq = new SequenceInputStream(stream1,
new SequenceInputStream(stream2, stream3));

The PushbackInputStream Class

The PushbackInputStream is a special stream that allows you to peek at a single character in an input stream and push it back onto the stream. This technique is often used in creating lexical scanners that peek at a character, put it back on the stream, and then read the character again as part of a larger input. For example, you might see that the next character is a digit, so you put it back and call your routine that reads in a number. At this point, you might be thinking that to create a PushbackInputStream filter you only need to pass it the input stream you want it to filter. You are correct:

public PushbackInputStream(InputStream inStream)

The unread method pushes a character back into the input stream:

public void unread(int ch) throws IOException

This character is the first one read the next time the stream is read. The character that gets pushed back does not have to be the most recent character read. In other words, you can read a character off a stream, then unread a completely different character. You can only unread one character at a time, however.

The StreamTokenizer Class

The StreamTokenizer class implements a simple lexical scanner that breaks up a stream of characters into a stream of tokens. If you think of a stream of characters as being a sentence, then the tokens represent the words and punctuation marks that make up the sentence. You create an StreamTokenizer filter by passing it the input stream you want it to filter:

public StringTokenizer(InputStream inStream)

After you have created the filter, you can use the nextToken method to retrieve that token from the stream:

public int nextToken() throws IOException

The nextToken method returns either a single character or one of the following constants:

If the token value returned is TT_WORD, the sval instance variable contains the actual value of the word:

public String sval

If the token value is TT_NUMBER, the nval instance variable contains the numeric value of the token:

public double nval

The TT_EOL and TT_EOF tokens represent the end of a line and the end of a file respectively.

You can specify which characters make up a word by calling the wordChars method with the starting and ending characters for a range of characters:

public void wordChars(int lowChar, int highChar)

The wordChars calls are additive, so subsequent calls to wordChars add to the possible word characters instead of replacing them. The default set of word characters is defined by the following calls:

tokenizer.wordChars(`A', `Z');     // All upper-case letters
tokenizer.wordChars(`a', `z');     // All lower-case letters
tokenizer.wordChars(150, 255);     // Other special characters
                                   // outside 7-bit ascii range

If you were writing a program to parse Java programs, you might also want to add `$' and `_' to the valid word chars, because these may appear in Java identifiers. You would do this with the following pair of calls:

tokenizer.wordChars(`$', `$');
tokenizer.wordChars(`_', `_');

One of the things that delimits a token is whitespace. Whitespace is not a token itself, but it can define where one token starts and another stops. For example, the phrase "Nyuk nyuk nyuk" contains three TT_WORD tokens, separated by whitespace. The phrase "Nyuk, nyuk, nyuk" actually contains five tokens--three nyuks and two `,' tokens. In each case, the whitespace is ignored. The typical whitespace characters are:

You can define the whitespace characters with the whitespaceChars method, which is also additive like the wordChars method:

public void whitespaceChars(int lowChar, int highChar)

You can define the default set of whitespace characters with:

tokenizer.whitespaceChars(` `, ` `);
tokenizer.whitespaceChars(`\t', `\t');
tokenizer.whitespaceChars(`\n', `\n');
tokenizer.whitespaceChars(`\r', `\r');
tokenizer.whitespaceChars(`\f', `\f');

Sun took a shortcut, however, and defined all control characters as whitespace, along with the space character. In other words, characters like escape and backspace are considered to be whitespace by the StreamTokenizer. Doing this allows the tokenizer to set its whitespace characters with a single call:

tokenizer.whitespaceChars(0, ` `);

The StreamTokenizer can also handle comments. It does not deal very well with multi- character comment characters, or with quote-like comments other than those that Java uses. It can handle //-style comments, and also the /*-*/ comments found in Java and C++, but it cannot handle the (*-*) comments found in Pascal. To allow the //-style comments to be parsed, pass true to the slashSlashComments method:

public void slashSlashComments(boolean allowSlashSlash)

To activate the /*-*/ comments, pass true to slashStarComments:

public void slashStarComments(boolean allowSlashStar)

In addition to these two methods, you can also flag an individual character as a comment character with the commentChar method:

public void commentChar(int commentChar)

The comment characters are considered to be single-line comments, which means that when one is encountered, the rest of the line is ignored and parsing begins again on the next line. The commentChar method is additive, so you can set multiple comment characters by calling this method multiple times.


CAUTION:
The StreamTokenizer sets / as a comment character by default. You may want to set it to be an ordinary character with the ordinaryChar method. Otherwise, any time the tokenizer encounters a single /, it will skip it and everything else up to the end of line.


You can undo any special settings for a character or a range of characters by calling the ordinaryChar or ordinaryChars methods:

public void ordinaryChar(int ch)
public void ordinaryChars(int loadChar, int highChar)

These methods undo any special significance to a character. For example, if you set the `$' and `_' characters to be word characters and then decide that they shouldn't be, you can make them ordinary characters again by using the following:

tokenizer.ordinaryChar(`$');
tokenizer.ordinaryChar(`_');

The StreamTokenizer also recognizes characters as quote characters. When the tokenizer encounters a quote character, it takes all the other characters up to the next quote character and puts them in the string value stored in sval, then it returns the quote character as the token value. You can flag a character as being a quote character by calling quoteChar:

public void quoteChar(int ch)

The default quote characters are ` and ".

Normally the words returned for a TT_WORD token are stored exactly as they appear in the input stream. However, if you want tokens to be case-insensitive--in other words, if you want FOO, Foo, and foo to be the same--you can ask the tokenizer to automatically convert words to all lowercase by passing true to the lowerCaseMode method:

public void lowerCaseMode(boolean shiftToLower)

The parseNumbers method tells the tokenizer to accept floating point numbers:

public void parseNumbers()

If this method is not called, the tokenizer treats the number 3.14159 as three separate tokens--3, ., and 14159. This method is called automatically by the StreamTokenizer constructor. The only time you need to call it is if you call resetSyntax.

The resetSyntax method completely clears out the tokenizer's tables:

public void resetSyntax()


CAUTION:
Because of the way StreamTokenizer is designed, it requires the space character (` `) to be something other than an ordinary character. If you call resetSyntax, you must then set space to be either white- space, a word character, a comment character, or a quote character. Otherwise, the tokenizer is unable to read characters from the stream.


The File Class

The File class encapsulates filesystem-related operations such as directory listing and creation, file removal and renaming, and file information. A File object can refer to either a file or a directory. You create a file object in one of three ways.

public File(String pathname)

creates a File instance corresponding to the directory or file named by pathname.

public File(String pathname, String filename)

creates a File instance corresponding to the file or directory named by filename in the directory named by pathname.

public File(File directory, String filename)

creates a File instance corresponding to the file or directory named by filename in the directory referenced by the File instance directory.


NOTE: Due to security restrictions, you may not use the File class or the file streams from within Netscape and other Web browsers. This is to prevent a malicious applet from reading and writing to your local hard drive.


Common Operations

Many of the operations in the File class are valid whether the object is a file or a directory. You can determine whether the object is a file or a directory with the isFile and isDirectory methods:

public boolean isFile()
public boolean isDirectory()

You can find out if the file or directory is readable or writable with the canRead and canWrite methods:

public boolean canRead()
public boolean canWrite()

A File object does not have to refer to an existing file. It can represent only a file name. You can determine if the File instance refers to an existing file or directory by calling the exists method:

public boolean exists()

The lastModified method returns a number indicating when the file or directory was last modified:

public long lastModified()

The value returned by the lastModified method is not guaranteed to be in any specific format. You should only use it to compare the modification times of two files, not as the exact modification time of a file.

Path names can either be relative or absolute. A relative path means that the path is relative to the current directory, while an absolute path does not depend on the current directory. An absolute path almost always starts with / under UNIX, or \ under Windows (or C:\). You can find out whether a File object is using an absolute path or a relative path by calling isAbsolute:

public boolean isAbsolute()

You can extract the various name portions of a file or directory using the following methods.

public String getName()

returns the actual name of the File object, without the preceding path name.

public String getParent()

returns the name of the directory containing the File object.

public String getPath()

returns the name of the File object including the path name, whether relative or absolute.

public String getAbsolutePath()

returns the absolute path name of the File object. If the file contains a relative path name, it figures out what the absolute path name would be and returns it.

The characters used for separating directory and file names in a path name are different across different operating systems. The File class provides character constants for the separator character--that is, the character between directory and file names--and also the path separator, which is the character between path names in a path list like the CLASSPATH. The separatorChar variable contains the character that separates file and directory names in a path:

public final static char separatorChar

This character is a / under UNIX, but a \ under Windows NT and Windows 95. The pathSeparatorChar contains the character that separates path names in a path list:

public final static char pathSeparatorChar

This character is ; under Windows NT/95 and : under UNIX.

You can rename a file or directory by passing the renameTo method a File object containing the new name:

public boolean renameTo(File newName)

The renameTo method returns true if the rename is successful. The delete method deletes a file, returning true if successful, but does not delete a directory:

public boolean delete()

The mkdir method treats the current File object as a directory name and tries to create a directory for that name, returning true if successful:

public boolean mkdir()

The mkdirs method is a special version of mkdir that creates all the necessary directories for the path named in the File object. In other words, if the path is for FOO/BAR/BAZ, it creates the FOO and BAR directories, too. The mkdirs method returns true if successful:

public boolean mkdirs()

Directory Operations

While most of the methods in the File class can be used on both files and directories, the list method is only for use in a directory:

public String[] list()

It returns an array of the names of all the files contained within the directory. You can also set up a filename filter for the list method, which allows you to select only certain filenames:

public String[] list(FilenameFilter filter)

The FilenameFilter interface defines a single method, accept, that returns true if a filename should be included in the list:

public abstract boolean accept(File dir, String name)

Listing 32.6 shows an object that implements a filename filter that allows only files ending with .java.

Listing 32.6Source Code for JavaFilter.java

import java.io.*;

// This class implements a filename filter that only allows
// files that end with .java

public class JavaFilter extends Object implements FilenameFilter
{
     public JavaFilter()
     {
     }

     public boolean accept(File dir, String name)
     {

// Only return true for accept if the file ends with .java
          return name.endsWith(".java");

     }
}
Listing 32.7 shows a program that uses the JavaFilter to list out all the .java files in the current directory.
Listing 32.7  Source code for ListJava.java.
import java.io.*;

public class ListJava extends Object
{
     public static void main(String[] args)
     {

// Create a File instance for the current directory
          File currDir = new File(".");

// Get a filtered list of the .java files in the current directory
          String[] javaFiles = currDir.list(new JavaFilter());

// Print out the contents of the javaFiles array
          for (int i=0; i < javaFiles.length; i++) {
               System.out.println(javaFiles[i]);
          }
     }
}

File Streams

The FileInputStream and FileOutputStream classes allow you to read and write files. You can create file streams from a file name string, a File instance, or a special file descriptor:

public FileInputStream(String filename)
public FileInputStream(File file)
public FileInputStream(FileDescriptor fd)
public FileOutputStream(String filename)
public FileOutputStream(File file)
public FileOutputStream(FileDescriptor fd)

The FileDescriptor class contains specific information about an open file. You never explicitly create a FileDescriptor yourself, you retrieve it from an open FileInputStream or FileOutputStream by calling the getFD method:

public final FileDescriptor getFD() throws IOException

The FileDescriptor class contains a single method, valid, that returns true if a file descriptor is valid:

public boolean valid()

The FileDescriptor class also provides static instance variables for the file descriptors for standard input, standard output, and standard error:

public final static FileDescriptor in
public final static FileDescriptor out
public final static FileDescriptor err

The following code fragment uses the FileDescriptor class to create the equivalent of System.in, System.out, and System.err:

InputStream systemIn = new FileInputStream(FileDescriptor.in);
PrintStream systemOut = new PrintStream(new FileOutputStream(FileDescriptor.out));
PrintStream systemErr = new PrintStream(new FileOutputStream(FileDescriptor.err));

The RandomAccessFile class

A random access file is similar to an input stream in that you can read data from it. At the same time, it is like an output stream because you can write data to it. The big difference between a random access file and a sequential access file (which is what a stream really is) is that you can instantly go to any section of a random access file and read or write. Think of a stream like an audio cassette. When you want to hear the next song, you have to fast-forward through the current song because you can only access the music sequentially. A CD, however, is more like a random access file, because if you want to hear the next song, you can tell it to skip right to that point.

When you create a random access file, you must give it a mode. The mode is either r for read-only or rw for read-write. If you open a random access file in read-only mode, you will not be able to write any data to it. There is no write-only mode. The constructors for the RandomAccessFile class are:

public RandomAccessFile(String filename, String mode)
throws IOException
public RandomAccessFile(File file, String mode)
throws IOException

The RandomAccessFile class implements all the methods in the DataInput and DataOutput interfaces. In addition, it implements a seek method that lets you jump to any position in the file instantly:

public void seek(long filePosition) throws IOException

You can determine the current file position, a useful thing if you plan on jumping back to this file position, using the getFilePointer method:

public long getFilePointer() throws IOException

The file position value used in the seek and getFilePointer methods is the number of bytes from the beginning of the file.