In this chapter you'll learn to use Java streams to perform sophisticated input and output using standard I/O, memory buffers, and files. You'll explore the input and output stream class hierarchy and learn to use stream filters to simplify I/O processing. You'll also learn how to perform random-access I/O and how to use the StreamTokenizer class to construct input parsers. When you finish this chapter, you'll be able to add sophisticated I/O processing to your Java programs.
Java input and output is based on the use of streams, or sequences of bytes that travel from a source to a destination over a communication path. If your program is writing to a stream, it is the stream's source. If it is reading from a stream, it is the stream's destination. The communication path is dependent on the type of I/O being performed. It can consist of memory-to-memory transfers, a file system, a network, and other forms of I/O.
Streams are not complicated. They are powerful because they abstract away the details of the communication path from input and output operations. This allows all I/O to be performed using a common set of methods. These methods can be tailored and extended to provide higher-level custom I/O capabilities.
Java defines two major classes of byte streams: InputStream and OutputStream. These streams are subclassed to provide a variety of I/O capabilities. Java 1.1 introduced the Reader and Writer classes to provide the foundation for 16-bit Unicode character- oriented I/O. These classes support internationalization of Java I/O. The Reader and Writer classes, such as InputStream and OutputStream, are subclassed to support additional capabilities. Unicode is covered in Chapter 19, "Internationalization."
Figure 17.1 identifies the java.io class hierarchy. As described in the previous section, the InputStream, OutputStream, Reader, and Writer classes are the major components of this hierarchy. Other high-level classes include the File, FileDescriptor, RandomAccessFile, ObjectStreamClass, and StreamTokenizer classes.
The InputStream and OutputStream classes have complementary subclasses. For example, both have subclasses for performing I/O via memory buffers, files, and pipes. The InputStream subclasses perform the input and the OutputStream classes perform the output.
FIGURE 17.1. The classes of the java.io hierarchy.
The InputStream class has seven direct subclasses. The ByteArrayInputStream class is used to convert an array into an input stream. The StreamBufferInputStream class uses a StreamBuffer as an input stream. The FileInputStream class allows files to be used as input streams. The ObjectInputStream class is used to read primitive types and objects that have been previously written to a stream. The PipedInputStream class allows a pipe to be constructed between two threads and supports input through the pipe. The SequenceInputStream class allows two or more streams to be concatenated into a single stream. The FilterInputStream class is an abstract class from which other input-filtering classes are constructed.
NOTE: The process of preparing objects for stream input and output is referred to as serialization. In order for an object to be serialized, it must implement the java.io.Serializable interface.
Filters are objects that read from one stream and write to another, usually altering the data in some way as they pass it from one stream to another. Filters can be used to buffer data, read and write objects, keep track of line numbers, and perform other operations on the data they move. Filters can be combined, with one filter using the output of another as its input. You can create custom filters by combining existing filters.
FilterInputStream has four filtering subclasses. The BufferedInputStream class maintains a buffer of the input data that it receives. This eliminates the need to read from the stream's source every time an input byte is needed. The DataInputStream class implements the DataInput interface, a set of methods that allow objects and primitive data types to be read from a stream. The LineNumberInputStream is used to keep track of input line numbers. The PushbackInputStream provides the capability to push data back onto the stream that it is read from so that it can be read again.
NOTE: Other Java API packages, such as java.util, contain classes and interfaces that extend those of java.io. In particular, the java.util package defines input and output stream classes that can be used to support file and stream compression.
The OutputStream class hierarchy consists of five direct subclasses. The ByteArrayOutputStream, FileOutputStream, ObjectOutputStream, and PipedOutputStream classes are the output complements to the ByteArrayInputStream, FileInputStream, ObjectInputStream, and PipedInputStream classes. The FilterOutputStream class provides subclasses that complement the FilterInputStream classes.
The BufferedOutputStream class is the output analog to the BufferedInputStream class. It buffers output so that output bytes can be written to devices in larger groups. The DataOutputStream class implements the DataOutput interface. This interface complements the DataInput interface. It provides methods that write objects and primitive data types to streams so that they can be read by the DataInput interface methods. The PrintStream class provides the familiar print() and println() methods used in most of the sample programs that you've developed so far in this book. It provides a number of overloaded methods that simplify data output.
NOTE: The PrintStream class is not necessarily used to print to a printer. Chapter 18, "Printing," covers printing to a printer.
The Reader class is similar to the InputStream class in that it is the root of an input class hierarchy. Reader supports 16-bit Unicode character input, while InputStream supports 8-bit byte input. The Reader class has six direct subclasses:
The Writer class is the output analog of the Reader class. It supports 16-bit Unicode character output. It has seven direct subclasses:
The File class is used to access the files and directories of the local file system. The FileDescriptor class is an encapsulation of the information used by the host system to track files that are being accessed. The RandomAccessFile class provides the capabilities needed to directly access data contained in a file. The ObjectStreamClass class is used to describe classes whose objects can be written (serialized) to a stream. The StreamTokenizer class is used to create parsers that operate on stream data.
New classes introduced with JDK 1.2 include the ObjectInputStream.GetField, ObjectOutputStream.PutField, and ObjectStreamField classes, which support object stream I/O. The FilePermission and SerializablePermission classes support I/O access controls (refer to Chapter 3, "The Extended Java Security Model").
NOTE: Other packages, such as java.util.zip, provide classes that extend the java.io class hierarchy shown in Figure 17.1.
The java.io package declares 10 interfaces. The DataInput and DataOutput interfaces provide methods that support machine-independent I/O. The ObjectInput and ObjectOutput interfaces extend DataInput and DataOutput to work with objects. The ObjectInputValidation interface supports the validation of objects that are read from a stream. The ObjectStreamConstants interface defines constants that are used to work with object streams. The Serializable, Externalizable, Replaceable, and Resolvable interfaces support the serialized writing of objects to streams. The FileFilter and FilenameFilter interfaces are used to select filenames from a list.
The InputStream class is an abstract class that lays the foundation for the Java Input class hierarchy. As such, it provides methods that are inherited by all InputStream classes.
THE READ() METHOD
The read() method is the most important method of the InputStream class hierarchy. It reads a byte of data from an input stream and blocks if no data is available. When a method blocks, it causes the thread in which it is executing to wait until data becomes available. This is not a problem in multithreaded programs. The read() method takes on several overloaded forms. It can read a single byte or an array of bytes, depending upon what form is used. It returns the number of bytes read, or -1 if an end of file is encountered with no bytes read.
The read() method is overridden and overloaded by subclasses to provide custom read capabilities.
The available()method returns the number of bytes that are available to be read without blocking. It is used to peek into the input stream to see how much data is available. However, depending on the input stream, it might not be accurate or useful. Some input streams on some operating systems may always report 0 available bytes. In general, it is not a good idea to blindly rely on this method to perform input processing.
The close() method closes an input stream and releases resources associated with the stream. It is always a good idea to close a stream to ensure that the stream processing is correctly terminated.
Java supports markable streams. These are streams that provide the capability to mark a position in the stream and then later reset the stream so that it can be reread from the marked position. If a stream can be marked, it must contain some memory associated with it to keep track of the data between the mark and the current position of the stream. When this buffering capability is exceeded, the mark becomes invalidated.
The markSupported() method returns a boolean value that identifies whether a stream supports mark and reset capabilities. The mark() method marks a position in the stream. It takes an integer parameter that identifies the number of bytes that can be read before the mark becomes invalid. This is used to set the buffering capacity of the stream. The reset()method simply repositions the stream to its last marked position.
The skip() method skips over a specified number of input bytes. It takes a long value as a parameter. You can use the skip() method to move to a specific position within an input stream.
The OutputStream class is an abstract class that lays the foundation for the output stream hierarchy. It provides a set of methods that are the output analog to the InputStream methods.
The write() method allows bytes to be written to the output stream. It provides three overloaded forms to write a single byte, an array of bytes, or a segment of an array. The write() method, like the read() method, may block when it tries to write to a stream. The blocking causes the thread executing the write() method to wait until the write operation has been completed.
NOTE: The OutputStream class defines three overloaded forms for the write() method. These forms allow you to write an integer, an array of bytes, or a subarray of bytes to an OutputStream object. You will often see several overloaded forms for methods that perform the same operation using different types of data.
The flush() method causes any buffered data to be immediately written to the output stream. Some subclasses of OutputStream support buffering and override this method to clean out their buffers and write all buffered data to the output stream. They must override the OutputStream flush() method because, by default, it does not perform any operations and is used as a placeholder.
It is generally more important to close() output streams than input streams, so that any data written to the stream is stored before the stream is deallocated and lost. The close() method of OutputStream is used in the same manner as that of InputStream.
Java supports byte array input and output via the ByteArrayInputStream and ByteArrayOutputStream classes. These classes use memory buffers as the source and destination of the input and output streams. These streams do not have to be used together. They are covered in the same section here because they provide similar and complementary methods. The StringBufferInputStream class is similar to the ByteArrayInput class and is also covered in this section.
The CharArrayReader, CharArrayWriter, StringReader, and StringWriter classes support character-based I/O in a similar manner to the ByteArrayInputStream, ByteArrayOutputStream, and StringBufferInputStream classes. They are covered later in this chapter.
The ByteArrayInputStream class creates an input stream from a memory buffer. The buffer is an array of bytes. It provides two constructors that use a byte array argument to create the input stream. The class does not support any new methods, but overrides the read(), skip(), available(), and reset() methods of InputStream.
The read() and skip() methods are implemented as specified for InputStream. The available() method is reliable and can be used to check on the number of available bytes in the buffer. The reset() method resets the stream to the marked position.
The ByteArrayOutputStream class is a little more sophisticated than its input complement. It creates an output stream on a byte array, but provides additional capabilities to allow the output array to grow to accommodate new data that is written to it. It also provides the toByteArray() and toString() methods for converting the stream to a byte array or String object.
ByteArrayOutputStream provides two constructors. One takes an integer argument that is used to set the output byte array to an initial size. The other constructor does not take an argument and sets the output buffer to a default size.
ByteArrayOutputStream provides some additional methods not declared for OutputStream. The reset() method resets the output buffer to allow writing to restart at the beginning of the buffer. The size() method returns the current number of bytes that have been written to the buffer. The writeTo() method is new. It takes an object of class OutputStream as an argument and writes the contents of the output buffer to the specified output stream. The write() methods override those of OutputStream to support array output.
Having learned about both sides of the byte array I/O classes, you now have a base from which to create a sample program. The source code of the ByteArrayIOApp program is provided in Listing 17.1.
import java.lang.System; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class ByteArrayIOApp { public static void main(String args[]) throws IOException { // Create ByteArrayOutputStream object ByteArrayOutputStream outStream = new ByteArrayOutputStream(); String s = "This is a test."; // Write output to stream for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); System.out.println("outstream: "+outStream); System.out.println("size: "+outStream.size()); ByteArrayInputStream inStream; inStream = new ByteArrayInputStream(outStream.toByteArray()); // Determine how many input bytes are available int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; // Read input into a byte array int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); } }
The program creates a ByteArrayOutputStream object, outStream, and an array, s, that contains the text "This is a test." to be written to the stream. Each s character is written, one at a time, to outStream. The contents of outstream are then printed, along with the number of bytes written.
A ByteArrayInputStream object, inStream, is created by invoking the toByteArray() method of outStream to create a byte array that is used as an argument to the ByteArrayInputStream constructor. The available() method is used to determine the number of available input bytes stored in the buffer. This number is stored as inBytes and is used to allocate a byte array to store the data that is read. The read() method is invoked for inStream to read inBytes worth of data. The actual number of bytes read is stored in bytesRead. This number is displayed, followed on the next line by the bytes that were read from inStream, as follows:
outstream: This is a test. size: 15 inStream has 15 available bytes 15 bytes were read They are: This is a test.
StringBufferInputStream is similar to ByteArrayInputStream except that it uses a StringBuffer to store input data. The input stream is constructed using a String argument. Its methods are identical to those provided by ByteArrayInputStream. The StringBufferInputStream was deprecated in Java 1.1. This means that it has been superceded. The StringReader class is now the preferred class for String-based input.
Java supports stream-based file input and output through the File, FileDescriptor, FileInputStream, and FileOutputStream classes. It supports direct or random access I/O using the File, FileDescriptor, and RandomAccessFile classes. Random access I/O is covered later in this chapter. The FileReader and FileWriter classes support Unicode-based file I/O. These classes are also covered later in this chapter.
The File class provides access to file and directory objects and supports a number of operations on files and directories. The FileDescriptor class encapsulates the information used by the host system to track files that are being accessed. The FileInputStream and FileOutputStream classes provide the capability to read and write to file streams.
The File class is used to access file and directory objects. It uses the file-naming conventions of the host operating system. The File class encapsulates these conventions using the File class constants.
File provides constructors for creating files and directories. These constructors take absolute and relative file paths and file and directory names.
The File class provides numerous access methods that can be used to perform all common file and directory operations. It is important for you to review the API page for this class because file I/O and file and directory operations are common to most programs.
File methods allow files to be created, deleted, and renamed. They provide access to a file's path/name and determine whether a File object is a file or directory. These methods also check read and write access permissions.
Directory methods allow directories to be created, deleted, renamed, and listed. Direct-ory methods also allow directory trees to be traversed by providing access to the parent and sibling directories.
The FileDescriptor class provides access to the file descriptors maintained by operating systems when files and directories are being accessed. This class is opaque in that it does not provide visibility into the specific information maintained by the operating system. It provides only one method, the valid() method, which is used to determine whether a file descriptor object is currently valid.
The FileInputStream class allows input to be read from a file in the form of a stream. Objects of class FileInputStream are created using a filename string or a File or FileDescriptor object as an argument. FileInputStream overrides the methods of the InputStream class and provides two new methods, finalize() and getFD(). The finalize() method is used to close a stream when it is processed by the Java garbage collector. The getFD() method is used to obtain access to the FileDescriptor associated with the input stream.
The FileOutputStream class allows output to be written to a file stream. Objects of class FileOutputStream are created in the same way as those of class FileInputStream, using a filename string, File object, or FileDescriptor object as an argument. FileOutputStream overrides the methods of the OutputStream class and supports the finalize() and getFD() methods described for the FileInputStream class.
The program in Listing 17.2 illustrates the use of the FileInputStream, FileOutputStream, and File classes. It writes a string to an output file and then reads the file to verify that the output was written correctly. The file used for the I/O is then deleted.
import java.lang.System; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; public class FileIOApp { public static void main(String args[]) throws IOException { // Create output file test.txt FileOutputStream outStream = new FileOutputStream("test.txt"); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); outStream.close(); // Open test.txt for input FileInputStream inStream = new FileInputStream("test.txt"); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); inStream.close(); File f = new File("test.txt"); f.delete(); } }
The FileOutputStream constructor creates an output stream on the file test.txt. The file is automatically created in the current working directory. It then writes the string "This is a test." to the output file stream. Note the similarity between this program and the previous one. The power of streams is that the same methods can be used no matter what type of stream is being used.
The output stream is closed to make sure that all the data is written to the file. The file is then reopened as an input file by creating an object of class FileInputStream. The same methods used in the ByteArrayIOApp program are used to determine the number of available bytes in the file and read these bytes into a byte array. The number of bytes read is displayed along with the characters corresponding to those bytes.
The input stream is closed and then a File object is created to provide access to the file. The File object is used to delete the file using the delete() method. The program's output follows:
inStream has 15 available bytes 15 bytes were read They are: This is a test.
The SequenceInputStream class is used to combine two or more input streams into a single input stream. The input streams are concatenated, which allows the individual streams to be treated as a single, logical stream. The SequenceInputStream class does not introduce any new access methods. Its power is derived from the two constructors that it provides. One constructor takes two InputStream objects as arguments. The other takes an Enumeration of InputStream objects. The Enumeration interface is described in Chapter 10, "Writing Console Applications." It provides methods for dealing with a sequence of related objects.
The program in Listing 17.3 reads the two Java source files, ByteArrayIOApp.java and FileIOApp.java, as a single file courtesy of the SequenceInputStream class.
import java.lang.System; import java.io.FileInputStream; import java.io.SequenceInputStream; import java.io.IOException; public class SequenceIOApp { public static void main(String args[]) throws IOException { SequenceInputStream inStream; FileInputStream f1 = new FileInputStream("ByteArrayIOApp.java"); FileInputStream f2 = new FileInputStream("FileIOApp.java"); // Concatentate two files into a single input stream inStream = new SequenceInputStream(f1,f2); boolean eof = false; int byteCount = 0; while (!eof) { int c = inStream.read(); if(c == -1) eof = true; else{ System.out.print((char) c); ++byteCount; } } System.out.println(byteCount+" bytes were read"); inStream.close(); f1.close(); f2.close(); } }
The program creates two objects of class FileInputStream for the files ByteArrayIOApp.java and FileIOApp.java. The SequenceInputClass constructor is used to construct a single input stream from the two FileInputStream objects. The program then uses a while loop to read all bytes in the combined file and display them to the console window. The loop stops when the end of the combined file is encountered. This is signaled when the read() method returns -1. The streams are closed after the combined files have been read. The program's output is as follows:
import java.lang.System; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class ByteArrayIOApp { public static void main(String args[]) throws IOException { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); System.out.println("outstream: "+outStream); System.out.println("size: "+outStream.size()); ByteArrayInputStream inStream; inStream = new ByteArrayInputStream(outStream.toByteArray()); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); } } import java.lang.System; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; public class FileIOApp { public static void main(String args[]) throws IOException { FileOutputStream outStream = new FileOutputStream("test.txt"); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); outStream.close(); FileInputStream inStream = new FileInputStream("test.txt"); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); inStream.close(); File f = new File("test.txt"); f.delete(); } } 1763 bytes were read
The SequenceIOApp program displays the combined contents of the two source files, followed by a line identifying the number of bytes that were read.
The filtered input and output stream classes provide the capability to filter I/O in a number of useful ways. I/O filters are used to adapt streams to specific program needs. These filters sit between an input stream and an output stream and perform special processing on the bytes they transfer from input to output. You can combine filters to perform a sequence of filtering operations where one filter acts on the output of another, as shown in Figure 17.2.
FIGURE 17.2. Combining filters.
The FilterInputStream class is an abstract class that is the parent of all filtered input stream classes. The FilterInputStream class provides the basic capability to create one stream from another. It allows one stream to be read and provided as output as another stream. This is accomplished through the use of the in variable, which is used to maintain a separate object of class InputStream. The design of the FilterInputStream class allows multiple chained filters to be created using several layers of nesting. Each subsequent class accesses the output of the previous class through the in variable. Because the in variable is an object of class InputStream, arbitrary InputStream objects can be filtered.
The FilterOutputStream class is the complement to the FilterInputStream class. It is an abstract class that is the parent of all filtered output stream classes. It is similar to the FilterInputStream class in that it maintains an object of class OutputStream as an out variable. Data written to an object of FilterOutputStream can be modified as needed to perform filtering operations and then forwarded to the out OutputStream object. Because out is declared to be of class OutputStream, arbitrary output streams can be filtered. Multiple FilterOutputStream objects can be combined in a manner that is analogous to FilterInputStream objects. The input of subsequent FilterOutputStream objects is linked to the output of preceding objects.
Buffered input and output is used to temporarily cache data that is read from or written to a stream. This allows programs to read and write small amounts of data without adversely affecting system performance. When buffered input is performed, a large number of bytes are read at a single time and stored in an input buffer. When a program reads from the input stream, the input bytes are read from the input buffer. Several reads may be performed before the buffer needs to be refilled. Input buffering is used to speed up overall stream input processing.
Output buffering is performed in a similar manner to input buffering. When a program writes to a stream, the output data is stored in an output buffer until the buffer becomes full or the output stream is flushed. Only then is the buffered output actually forwarded to the output stream's destination.
Java implements buffered I/O as filters. The filters maintain and operate the buffer that sits between the program and the source or destination of a buffered stream.
The BufferedInputStream class supports input buffering by automatically creating and maintaining a buffer for a designated input stream. This allows programs to read data from the stream one byte at a time without degrading system performance. Because the BufferedInputStream class is a filter, it can be applied to arbitrary objects of class InputStream and combined with other input filters.
The BufferedInputStream class uses several variables to implement input buffering. These variables are described in the Java API page for this class. However, because these variables are declared as protected, they cannot be directly accessed by your program.
BufferedInputStream defines two constructors. One allows the size of an input buffer to be specified and the other does not. Both constructors take an object of class InputStream as an argument. It is usually better to let BufferedInputStream select the best size for the input buffer than to specify one yourself, unless you have specific knowledge that one buffer size is better than another.
BufferedInputStream overrides the access methods provided by InputStream and does not introduce any new methods of its own.
The BufferedOutputStream class performs output buffering in a manner that is analogous to BufferedInputStream. It allows the size of the output buffer to be specified in a constructor as well as providing for a default buffer size. It overrides the methods of the OutputStream class and does not introduce any new methods of its own.
The BufferedIOApp program (see Listing 17.4) builds on the SequenceIOApp example that was previously presented. It performs buffering on the SequenceInputStream object used to combine the input from two separate files. It also performs buffering on program output so that characters do not need to be displayed to the console window a single character at a time.
import java.lang.System; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.FileInputStream; import java.io.SequenceInputStream; import java.io.IOException; public class BufferedIOApp { public static void main(String args[]) throws IOException { SequenceInputStream f3; FileInputStream f1 = new FileInputStream("ByteArrayIOApp.java"); FileInputStream f2 = new FileInputStream("FileIOApp.java"); f3 = new SequenceInputStream(f1,f2); // Create the buffered input and output streams BufferedInputStream inStream = new BufferedInputStream(f3); BufferedOutputStream outStream = new BufferedOutputStream(System.out); inStream.skip(500); boolean eof = false; int byteCount = 0; while (!eof) { int c = inStream.read(); if(c == -1) eof = true; else{ outStream.write((char) c); ++byteCount; } } String bytesRead = String.valueOf(byteCount); bytesRead+=" bytes were read\n"; outStream.write(bytesRead.getBytes(),0,bytesRead.length()); inStream.close(); outStream.close(); f1.close(); f2.close(); } }
The program begins by creating two objects of FileInputStream and combining them into a single input stream using the SequenceInputStream constructor. It then uses this stream to create an object of BufferedInputStream using the default buffer size.
A BufferedOutputStream object is created using the System.out output stream and a default buffer size. The skip() method is used to skip over 500 bytes of the input stream. This is done for two reasons: to illustrate the use of the skip() method and to cut down on the size of the program output. The rest of the input is read and printed, as in the previous example.
The program output is similar to that of the preceding example. You should execute BufferedIOApp from the \ch17 directory. The skip() method was used to skip over 500 bytes of input. These bytes are also absent from the program's output, which is as follows:
rrayInputStream inStream; inStream = new ByteArrayInputStream(outStream.toByteArray()); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); } } import java.lang.System; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; public class FileIOApp { public static void main(String args[]) throws IOException { FileOutputStream outStream = new FileOutputStream("test.txt"); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); outStream.close(); FileInputStream inStream = new FileInputStream("test.txt"); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; int bytesRead = inStream.read(inBuf,0,inBytes); System.out.println(bytesRead+" bytes were read"); System.out.println("They are: "+new String(inBuf)); inStream.close(); File f = new File("test.txt"); f.delete(); } } 1263 bytes were read
PushbackInputStream is a filter that lets you push a byte that was previously read back onto the input stream so that it can be reread. This type of filter is commonly used with parsers. When a character indicating a new input token is read, it is pushed back onto the input stream until the current input token is processed. It is then reread when processing of the next input token is initiated. PushbackInputStream allows only a single byte to be pushed back. This is generally enough for most applications.
The pushback character is stored in a variable named pushBack.
The unread() method is the only new method introduced by this class. It is used to push a specified character back onto the input stream.
The PushbackIOApp program illustrates the use of the PushbackInputStream class (see Listing 17.5). It adds a pushback filter to the ByteArrayIOApp program shown earlier in this chapter.
import java.lang.System; import java.io.PushbackInputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; public class PushbackIOApp { public static void main(String args[]) throws IOException { ByteArrayOutputStream outStream = new ByteArrayOutputStream(); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); System.out.println("outstream: "+outStream); System.out.println("size: "+outStream.size()); ByteArrayInputStream inByteArray; inByteArray = new ByteArrayInputStream(outStream.toByteArray()); PushbackInputStream inStream; inStream = new PushbackInputStream(inByteArray); char ch = (char) inStream.read(); System.out.println("First character of inStream is "+ch); // Push `t' back onto stream inStream.unread((int) `t'); int inBytes = inStream.available(); System.out.println("inStream has "+inBytes+" available bytes"); byte inBuf[] = new byte[inBytes]; for(int i=0;i<inBytes;++i) inBuf[i]=(byte) inStream.read(); System.out.println("They are: "+new String(inBuf)); } }
PushbackIOApp creates a stream to be used for byte array input using the code of the ByteArrayIOApp program. It applies a pushback filter to this stream by using the PushbackInputStream filter to create an object of class PushbackInputStream. It reads the first character of the input stream and displays it. It then pushes back a t onto the input stream. Note that any character could have been pushed back upon the input stream. The new input stream is then read and displayed.
The program output shows how the pushback filter was used to change the first character of the input stream from an uppercase T to a lowercase t. The program output consists of the following:
outstream: This is a test. size: 15 First character of inStream is T inStream has 15 available bytes They are: this is a test.
The LineNumberInputStream class provides a handy capability for keeping track of input line numbers. It is also a subclass of FilterInputStream. This class provides two new methods to support line number processing. The setLineNumber() method is used to set the current line number to a particular value. The getLineNumber() method is used to obtain the value of the current line number.
Up until Java 1.1, the LineNumberInputStream class was the preferred class for tracking input line numbers. In Java 1.1, significant support was added for internationalization. As a result, the LineNumberInputStream class has been deprecated. The LineNumberReader class (covered later in this chapter) is now the preferred class for tracking input line numbers.
The DataInputStream and DataOutputStream classes implement the DataInput and DataOutput interfaces. These interfaces identify methods that allow primitive data types to be read from and written to a stream. By implementing these interfaces, the DataInputStream and DataOutputStream classes provide the basis for implementing portable input and output streams.
The DataInputStream class provides the capability to read arbitrary objects and primitive types from an input stream. It implements the methods of the DataInput interface. These methods provide a full range of input capabilities:
Note that most, but not all, of these methods raise the EOFException when an end of file is encountered. The readLine() method returns a null value to signify a read past the end of a file. This method was deprecated in Java 1.1. The readLine() method of the BufferedReader class should be used instead. BufferedReader provides better support for internationalization. Its readLine() method corrects errors that exist in the readLine() method of DataInputStream.
The DataOutputStream class provides an output complement to DataInputStream. It allows arbitrary objects and primitive data types to be written to an output stream. It also keeps track of the number of bytes written to the output stream. It is an output filter and can be combined with any output-filtering streams.
The program in Listing 17.6 shows how DataInputStream and DataOutputStream can be used to easily read and write a variety of values using streams.
import java.lang.System; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; public class DataIOApp { public static void main(String args[]) throws IOException { File file = new File("test.txt"); FileOutputStream outFile = new FileOutputStream(file); DataOutputStream outStream = new DataOutputStream(outFile); // Write various data types to the output stream outStream.writeBoolean(true); outStream.writeInt(123456); outStream.writeChar(`j'); outStream.writeDouble(1234.56); System.out.println(outStream.size()+" bytes were written"); outStream.close(); outFile.close(); FileInputStream inFile = new FileInputStream(file); DataInputStream inStream = new DataInputStream(inFile); System.out.println(inStream.readBoolean()); System.out.println(inStream.readInt()); System.out.println(inStream.readChar()); System.out.println(inStream.readDouble()); inStream.close(); inFile.close(); file.delete(); } }
The program creates an object of class File that is used to access the test.txt file. This object is used to create an instance of class FileOutputStream that is assigned to the outFile variable. An object of class DataOutputStream is then constructed as a filter for the FileOutputStream object.
The writeBoolean(), writeChar(), writeInt(), and writeDouble() methods of DataOutputStream are used to write examples of primitive data types to the filtered output stream. The number of bytes written to the output stream is determined by the size() method and displayed to the console window. The output streams are then closed.
The File object, created at the beginning of the program, is then used to create an object of class FileInputStream. The output stream is then filtered by creating an object of DataInputStream.
The primitive data types that were written to the output file in the beginning of the program are now read from the filtered input stream and displayed to the console window.
The program's output shows that the data values were successfully written and read using the data I/O filters:
15 bytes were written true 123456 j 1234.56
The PrintStream class should be no stranger to you. The System.out object that you have been using for most of the sample programs is an instance of the PrintStream class. It is used to write output to the Java console window.
PrintStream's power lies in the fact that it provides two methods, print() and println(), that are overloaded to print any primitive data type or object. Objects are printed by first converting them to strings using their toString() method, inherited from the Object class. To provide custom printing for any class, all you have to do is override the toString() method for that class.
PrintStream provides the capability to automatically flush all output bytes in the stream when a new line character is written to the stream. This feature can be enabled or disabled when the stream is created.
Because PrintStream is a filter, it takes an instance of OutputStream as an argument to its constructor. A second constructor adds the capability to use the autoflushing feature.
PrintStream introduces only one new method besides the extensively overloaded print() and println() methods. The checkError() method is used to flush stream output and determine whether an error occurred on the output stream. This capability is useful for printing output to devices, such as printers, where error status is needed to notify the user of any changes to the device state.
Piped I/O provides the capability for threads to communicate via streams. A thread sends data to another thread by creating an object of PipedOutputStream that it connects to an object of PipedInputStream. The output data written by one thread is read by another thread using the PipedInputStream object.
The process of connecting piped input and output threads is symmetric. An object of class PipedInputThread can also be connected to an existing object of class PipedOutputThread.
Java automatically performs synchronization with respect to piped input and output streams. The thread that reads from an input pipe does not have to worry about any conflicts with tasks that are being written to the corresponding output stream thread.
Both PipedInputStream and PipedOutputStream override the standard I/O methods of InputStream and OutputStream. The only new method provided by these classes is the connect() method. Both classes provide the capability to connect a piped stream when it is constructed by passing the argument of the piped stream to which it is to be connected as an argument to the constructor.
The PipedIOApp program creates two threads of execution, named Producer and Consumer, that communicate using connected objects of classes PipedOutputStream and PipedInputStream. Producer sends the message "This is a test." to Consumer one character at a time, and Consumer reads the message in the same manner. Producer displays its name and any characters that it writes to the console window. Consumer reads the message and displays its name and the characters it reads to the console window. The source code for the PipedIOApp program is shown in Listing 17.7.
import java.lang.Thread; import java.lang.System; import java.lang.InterruptedException; import java.lang.Runnable; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.IOException; class PipedIOApp { public static void main(String args[]) { Thread thread1 = new Thread(new PipeOutput("Producer")); Thread thread2 = new Thread(new PipeInput("Consumer")); thread1.start(); thread2.start(); boolean thread1IsAlive = true; boolean thread2IsAlive = true; do { if(thread1IsAlive && !thread1.isAlive()){ thread1IsAlive = false; System.out.println("Thread 1 is dead."); } if(thread2IsAlive && !thread2.isAlive()){ thread2IsAlive = false; System.out.println("Thread 2 is dead."); } }while(thread1IsAlive || thread2IsAlive); } } class PipeIO { static PipedOutputStream outputPipe = new PipedOutputStream(); static PipedInputStream inputPipe = new PipedInputStream(); static { try { // Connect input and output pipes outputPipe.connect(inputPipe); }catch (IOException ex) { System.out.println("IOException in static initializer"); } } String name; public PipeIO(String id) { name = id; } } class PipeOutput extends PipeIO implements Runnable { public PipeOutput(String id) { super(id); } public void run() { String s = "This is a test."; try { for(int i=0;i<s.length();++i){ outputPipe.write(s.charAt(i)); System.out.println(name+" wrote "+s.charAt(i)); } outputPipe.write(`!'); } catch(IOException ex) { System.out.println("IOException in PipeOutput"); } } } class PipeInput extends PipeIO implements Runnable { public PipeInput(String id) { super(id); } public void run() { boolean eof = false; try { while (!eof) { int inChar = inputPipe.read(); if(inChar != -1) { char ch = (char) inChar; if(ch=='!'){ eof=true; break; }else System.out.println(name+" read "+ch); } } } catch(IOException ex) { System.out.println("IOException in PipeOutput"); } } }
This program is somewhat longer than the other examples in this chapter due to the overhead needed to set up the threading. The main() method creates the two Producer and Consumer threads as objects of classes PipeOutput and PipeInput. These classes are subclasses of PipeIO that implement the Runnable interface. The main() method starts both threads and then loops, checking for their death.
The PipeIO class is the superclass of the PipeOutput and PipeInput classes. It contains the static variables, outputPipe and inputPipe, that are used for interthread communication. These variables are assigned objects of classes PipedOutputStream and PipeInputStream. The static initializer is used to connect outputPipe with inputPipe using the connect() method. The PipeIO constructor provides the capability to maintain the name of its instances. This is used by the PipeInput and PipeOutput classes to store thread names.
The PipeOutput class extends PipeIO and implements the Runnable interface, making it eligible to be executed as a separate thread. The required run() method performs all thread processing. It loops to write the test message one character at a time to the outputPipe. It also displays its name and the characters that it writes to the console window. The ! character is used to signal the end of the message transmission. Notice that IOException is handled within the thread rather than being identified in the throws clause of the run() method. In order for run() to properly implement the Runnable interface, it cannot throw any exceptions.
The PipeInput class also extends PipeIO and implements the Runnable interface. It simply loops and reads a character at a time from inputPipe, displaying its name and the characters that it reads to the console window. It also handles IOException in order to avoid having to identify the exception in its throws clause.
The output of PipeIOApp shows the time sequencing of the thread input and output taking place using the connected pipe I/O streams. The output generated by running the program on your computer will probably differ because of differences in your computer's execution speed and I/O performance. The output generated when I ran the program is as follows:
Producer wrote T Producer wrote h Producer wrote i Producer wrote s Producer wrote Consumer read T Consumer read h Consumer read i Producer wrote i Producer wrote s Producer wrote Consumer read s Consumer read Producer wrote a Producer wrote Producer wrote t Consumer read i Consumer read s Consumer read Producer wrote e Producer wrote s Consumer read a Consumer read Consumer read t Producer wrote t Producer wrote . Thread 1 is dead. Consumer read e Consumer read s Consumer read t Consumer read . Thread 2 is dead.
The ObjectOutputStream and ObjectInputStream classes allow objects and values of primitive types to be written to and read from streams. These classes implement the ObjectOutput and ObjectInput interfaces. Of the methods specified by ObjectOutput, the writeObject() method is the most interesting; it writes objects that implement the Serializable interface to a stream. The ObjectInput interface provides the readObject() method to read the objects written to a stream by the writeObject() method.
The Serializable interfaces are used to identify objects that can be written to a stream. It does not define any constants or methods, but it does place some constraints on which classes are serializable. Chapter 40, "Using Object Serialization and JavaSpaces," covers the Serializable interface and object I/O in detail.
The ObjectIOApp program, shown in Listing 17.8, shows how the ObjectOutputStream and ObjectInputStream classes can be used to write and read objects from streams.
import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.File; import java.io.IOException; import java.util.Date; public class ObjectIOApp { public static void main(String args[]) throws IOException, ClassNotFoundException { File file = new File("test.txt"); FileOutputStream outFile = new FileOutputStream(file); ObjectOutputStream outStream = new ObjectOutputStream(outFile); TestClass1 t1 = new TestClass1(true,9,'A',0.0001,"java"); TestClass2 t2 = new TestClass2(); String t3 = "This is a test."; Date t4 = new Date(); // Write objects to stream outStream.writeObject(t1); outStream.writeObject(t2); outStream.writeObject(t3); outStream.writeObject(t4); outStream.close(); outFile.close(); FileInputStream inFile = new FileInputStream(file); ObjectInputStream inStream = new ObjectInputStream(inFile); // Read objects from stream and display them System.out.println(inStream.readObject()); System.out.println(inStream.readObject()); System.out.println(inStream.readObject()); System.out.println(inStream.readObject()); inStream.close(); inFile.close(); file.delete(); } } class TestClass1 implements Serializable { boolean b; int i; char c; double d; String s; TestClass1(boolean b,int i,char c,double d,String s){ this.b = b; this.i = i; this.c = c; this.d = d; this.s = s; } public String toString(){ String r = String.valueOf(b)+" "; r += String.valueOf(i)+" "; r += String.valueOf(c)+" "; r += String.valueOf(d)+" "; r += String.valueOf(s); return r; } } class TestClass2 implements Serializable { int i; TestClass1 tc1; TestClass1 tc2; TestClass2(){ i=0; tc1 = new TestClass1(true,2,'j',1.234,"Java"); tc2 = new TestClass1(false,7,'J',2.468,"JAVA"); } public String toString(){ String r = String.valueOf(i)+" "; r += tc1.toString()+" "; r += tc2.toString(); return r; } }
ObjectIOApp is similar in design to the DataIOApp program in Listing 17.6. It creates a File object to support I/O to the test.txt file. The File object is used to create an object of class FileOutputStream. This object is then used to create an object of class ObjectOutputStream, which is assigned to the outStream variable.
Four objects are created and assigned to the t1 through t4 variables. An object of class TestClass1 is assigned to the t1 variable, and an object of class TestClass2 is assigned to the t2 variable. The TestClass1 and TestClass2 classes are declared at the end of Listing 17.8. A String object is assigned to t3, and a Date object is assigned to t4.
The objects referenced by the t1 through t4 variables are written to outStream using the writeObject() method. The stream and file are then closed. The test.txt file is reopened as a FileInputStream object, which is then converted to an ObjectInputStream object and assigned to the inStream variable. Four objects are read from inStream, using the readObject() method, and then written to standard output. The program's output is as follows:
true 9 A 1.0E-4 java 0 true 2 j 1.234 Java false 7 J 2.468 JAVA This is a test. Thu Jan 15 17:43:16 PST 1998
Note that you'll receive a different date value from this one. TestClass1 and TestClass2 are dummy test classes that are used to make the example work. Their toString() methods are automatically invoked by the println() method to convert objects to string values for printing.
The Reader and Writer classes are abstract classes at the top of a class hierarchy that support the reading and writing of Unicode character streams. These classes were introduced with Java 1.1.
The Reader class supports the standard read(), reset(), skip(), mark(), markSupported(), and close() methods. In addition to these, the ready() method returns a boolean value that indicates whether the next read operation will succeed without blocking.
The direct subclasses of the Reader class are BufferedReader, CharArrayReader, FilterReader, InputStreamReader, PipedReader, and StringReader.
The Writer class is the output complement to the Reader class. It declares the write(), flush(), and close() methods. Its direct subclasses are BufferedWriter, CharArrayWriter, FilterWriter, OutputStreamWriter, PipedWriter, StringWriter, and PrintWriter. Each of these subclasses, except PrintWriter, is an output complement to a Reader subclass.
The CharArrayReader and CharArrayWriter classes are similar to the ByteArrayInputStream and ByteArrayOutputStream classes in that they support I/O from memory buffers. The difference between these classes is that CharArrayReader and CharArrayWriter support 16-bit character I/O, and ByteArrayInputStream and ByteArrayOutputStream support 8-bit byte array I/O.
The CharArrayReader class does not add any new methods to those provided by Reader. The CharArrayWriter class adds the following methods to those provided by Writer:
These methods are similar to those provided by the ByteArrayOutputStream class.
The StringReader class provides the capability to read character input from a string. Like CharArrayReader, it does not add any additional methods to those provided by Reader. The StringWriter class is used to write character output to a StringBuffer object. It adds the getBuffer() and toString() methods. The getBuffer() method returns the StringBuffer object corresponding to the output buffer. The toString() method returns a String copy of the output buffer.
The CharArrayIOApp program (see Listing 17.9) is based on the ByteArrayIOApp program (see Listing 17.1) introduced at the beginning of this chapter. It writes the string "This is a test." one character at a time to a CharArrayWriter object. It then converts the output buffer to a CharArrayReader object. Each character of the input buffer is read and appended to a StringBuffer object. The StringBuffer object is then converted to a String object. The number of characters read and the String object are then displayed. The program output follows:
outstream: This is a test. size: 15 15 characters were read They are: This is a test.
The StringIOApp program (see Listing 17.10) is similar to CharArrayIOApp. It writes output to a StringBuffer instead of a character array. It produces the same output as CharArrayIOApp.
import java.lang.System; import java.io.CharArrayReader; import java.io.CharArrayWriter; import java.io.IOException; public class CharArrayIOApp { public static void main(String args[]) throws IOException { CharArrayWriter outStream = new CharArrayWriter(); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); System.out.println("outstream: "+outStream); System.out.println("size: "+outStream.size()); CharArrayReader inStream; inStream = new CharArrayReader(outStream.toCharArray()); int ch=0; StringBuffer sb = new StringBuffer(""); while((ch = inStream.read()) != -1) sb.append((char) ch); s = sb.toString(); System.out.println(s.length()+" characters were read"); System.out.println("They are: "+s); } }
import java.lang.System; import java.io.StringReader; import java.io.StringWriter; import java.io.IOException; public class StringIOApp { public static void main(String args[]) throws IOException { StringWriter outStream = new StringWriter(); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); System.out.println("outstream: "+outStream); System.out.println("size: "+outStream.toString().length()); StringReader inStream; inStream = new StringReader(outStream.toString()); int ch=0; StringBuffer sb = new StringBuffer(""); while((ch = inStream.read()) != -1) sb.append((char) ch); s = sb.toString(); System.out.println(s.length()+" characters were read"); System.out.println("They are: "+s); } }
The InputStreamReader and OutputStreamWriter classes are used to convert between byte streams and character streams. The InputStreamReader class converts an object of an InputStream subclass into a character-oriented stream. The OutputStreamWriter class converts a character output stream to a byte output stream.
The InputStreamReader() constructor takes an InputStream object as a parameter and creates an InputStreamReader object. This provides a bridge between byte-oriented input streams and character-oriented input streams. A second InputStreamReader constructor also takes a String parameter that identifies the character encoding to be used in byte-to-character conversion. The getEncoding() method may be used to retrieve the encoding that is in effect. The ready() method is used to determine whether a character can be read without blocking.
The InputConversionApp program, shown in Listing 17.11, converts the standard input stream (System.in) from a byte stream to a character stream. The input characters are echoed to standard output. It also prints out the encoding that is in effect on your system. The following is an example of the output generated when the program is run on my computer:
Encoding: 8859_1
>This is a test. This is a test. >
The 8859_1 encoding is the International Standard Organization (ISO) Latin-1 character encoding. Chapter 19, "Internationalization," identifies other encodings.
import java.lang.System; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; public class InputConversionApp { public static void main(String args[]) throws IOException { InputStreamReader in = new InputStreamReader(System.in); BufferedReader inStream = new BufferedReader(in); // Get the encoding that is in use System.out.println("Encoding: "+in.getEncoding()); String inputLine; do { System.out.print(">"); System.out.flush(); inputLine=inStream.readLine(); System.out.println(inputLine); } while (inputLine.length() != 0); } }
The OutputStreamWriter class allows a character stream to be converted to a byte stream. Its constructor takes the name of an object of an OutputStream subclass as a parameter. The characters written to an OutputStreamWriter object are translated and written to the OutputStream object specified in the OutputStreamWriter object's constructor. The translation is performed according to the encoding specified in the System property file.encoding. A different encoding scheme may be specified by supplying the name of the encoding scheme in the OutputStreamWriter constructor. The getEncoding() method may be used to retrieve the current character encoding that is in effect.
The FileReader and FileWriter classes are subclasses of InputStreamReader and OutputStreamWriter that are used to perform character-based file I/O. These classes do not provide any additional access methods. However, their constructors provide the capability to create input and output character streams using String objects that represent filenames, File objects, and FileDescriptor objects.
Listing 17.12 demonstrates the use of the FileReader and FileWriter classes. It converts the FileIOApp program that was introduced earlier in the chapter (see Listing 17.2) to character-oriented I/O and produces the following output:
15 characters were read They are: This is a test.
The main difference between CharFileIOApp and FileIOApp is that FileReader and FileWriter classes are used instead of the FileInputStream and FileOutputStream classes. The other difference is the use of a StringBuffer object (instead of a byte array) to capture the characters read from the input file stream.
import java.lang.System; import java.io.FileReader; import java.io.FileWriter; import java.io.File; import java.io.IOException; public class CharFileIOApp { public static void main(String args[]) throws IOException { FileWriter outStream = new FileWriter("test.txt"); String s = "This is a test."; for(int i=0;i<s.length();++i) outStream.write(s.charAt(i)); outStream.close(); FileReader inStream = new FileReader("test.txt"); StringBuffer sb = new StringBuffer(""); int ch=0; while((ch = inStream.read()) != -1) sb.append((char) ch); s = sb.toString(); System.out.println(s.length()+" characters were read"); System.out.println("They are: "+s); inStream.close(); File f = new File("test.txt"); f.delete(); } }
Buffered Character I/O
Buffered character I/O is supported by the BufferedReader and BufferedWriter classes. These classes are character-based analogs to the BufferedInputStream and BufferedOutputStream classes. In Java 1.1, the readLine() method of the BuffereddReader class replaced the readLine() method of the DataInputStream class for reading lines of text from the console, a file, or other character-oriented input streams.
The BufferedWriter class provides the capability to write buffered data to character-based output streams. It adds the newLine() method to the methods that it inherits (and overrides) from the Writer class. The newLine() method allows new line characters to be written in a system-independent manner. It is preferable to simply writing an \n character to the output stream. The line.separator system property defines the system- specific new line character.
The LineNumberReader class is a subclass of the BufferedReader class that is used to associate line numbers with each line of text that is read from a stream. Lines are terminated by a new line character (\n), a carriage return (\r), or a carriage return-new line combination (\r\n).
In addition to the methods that it inherits from BufferedReader, the LineNumberReader class declares the getLineNumber() and setLineNumber() methods. The getLineNumber() method returns the current line number. The setLineNumber() method sets the current line number to an integer value.
The LineNumberIOApp program (see Listing 17.13) illustrates the use of the LineNumberReader class. It creates a FileReader on the LineNumberIOApp.java source file and then uses the FileReader object to create a LineNumberReader object. The character file is read, one line at a time, and its contents are displayed using line numbers obtained via the getLineNumber() method. The output of this program follows:
1. import java.lang.System; 2. import java.io.LineNumberReader; 3. import java.io.FileReader; 4. import java.io.BufferedWriter; 5. import java.io.IOException; 6. 7. public class LineNumberIOApp { 8. public static void main(String args[]) throws IOException { 9. FileReader inFile = new FileReader("LineNumberIOApp.java"); 10. LineNumberReader inLines = new LineNumberReader(inFile); 11. String inputLine; 12. while ((inputLine=inLines.readLine()) != null) { 13. System.out.println(inLines.getLineNumber()+". "+inputLine); 14. } 15. } 16. }
import java.lang.System; import java.io.LineNumberReader; import java.io.FileReader; import java.io.BufferedWriter; import java.io.IOException; public class LineNumberIOApp { public static void main(String args[]) throws IOException { FileReader inFile = new FileReader("LineNumberIOApp.java"); LineNumberReader inLines = new LineNumberReader(inFile); String inputLine; while ((inputLine=inLines.readLine()) != null) { // Get and print the line number System.out.println(inLines.getLineNumber()+". "+inputLine); } } }
The FilterReader and FilterWriter classes are character-oriented analogs of the FilterInputStream and FilterOutputStream classes. The FilterReader class uses the in variable for input filtering and FilterWriter class uses the out variable for output filtering. Consult the section "Filtered I/O" earlier in this chapter for a description of I/O filtering.
The PushbackReader class is a subclass of FilterReader that provides the capability to push a character that was previously read back onto the input stream so that it can be read again. It is the character-oriented analog of the PushbackInputStream class that you studied earlier in the chapter.
The PipedReader and PipedWriter classes support character-oriented piped I/O in the same way that PipedInputStream and PipedOutputStream support byte-oriented piped I/O. Consult the section "Piped I/O" earlier in this chapter for a description of piped input and output.
The PrintWriter class is the character-oriented replacement for the PrintStream class. The PrintWriter class improves PrintStream by using a platform-dependent line separator to print lines instead of the new line (\n) character. The System line.separator property identifies the system unique line separator. PrintWriter also provides better support for Unicode characters than PrintStream. The checkError() method is used to flush printed output and test for an error condition. The setError() method is used to set an error condition. PrintWriter provides support for printing primitive data types, character arrays, strings, and general objects. Objects are converted to a string (via the inherited or overridden toString() method) before being printed.
The RandomAccessFile class provides the capability to perform I/O directly to specific locations within a file. The name random access comes from the fact that data can be read from or written to random locations within a file rather than as a continuous stream of information. Random access is supported through the seek() method, which allows the pointer corresponding to the current file position to be set to arbitrary locations within the file.
RandomAccessFile implements both the DataInput and DataOuput interfaces. This provides the capability to perform I/O using primitive data types.
RandomAccessFile also supports basic file read/write permissions, allowing files to be accessed in read-only or read-write modes. A mode stream argument is passed to the RandomAccessFile constructor as r or rw, indicating read-only and read-write file access. The read-only access attribute may be used to prevent a file from being inadvertently modified.
RandomAccessFile introduces several new methods besides those inherited from Object and implemented from DataInput and DataOutput. These methods include seek(), getFilePointer(), and length(). The seek() method sets the file pointer to a particular location within the file. The getFilePointer() method returns the current location of the file pointer. The length()method returns the length of the file in bytes.
The RandomIOApp program provides a simple demonstration of the capabilities of random-access I/O. It writes a boolean, int, char, and double value to a file and then uses the seek() method to seek to offset location 1 within the file. This is the position after the first byte in the file. It then reads the int, char, and double values from the file and displays them to the console window. Next, it moves the file pointer to the beginning of the file and reads the boolean value that was first written to the file. This value is also written to the console window. The source code of the RandomIOApp program is shown in Listing 17.14.
import java.lang.System; import java.io.RandomAccessFile; import java.io.IOException; public class RandomIOApp { public static void main(String args[]) throws IOException { RandomAccessFile file = new RandomAccessFile("test.txt","rw"); file.writeBoolean(true); file.writeInt(123456); file.writeChar(`j'); file.writeDouble(1234.56); // Use seek() to move to a specific file location file.seek(1); System.out.println(file.readInt()); System.out.println(file.readChar()); System.out.println(file.readDouble()); file.seek(0); System.out.println(file.readBoolean()); file.close(); } }
Although the processing performed by RandomIOApp is quite simple, it illustrates how random I/O allows you to move the file pointer to various locations within a file to directly access values and objects contained within the file.
The program's output is as follows:
123456 j 1234.56 true
The StreamTokenizer class is used by parsers to convert an input character stream into a stream of lexical tokens. It uses special methods to identify parser parameters, such as ordinary, whitespace, quote, and comment characters. These methods also enable and disable number and end-of-line parsing.
Seven variables are defined for the StreamTokenizer class, four of which are constant class variables. The TT_EOF, TT_EOL, TT_NUMBER, and TT_WORD constants are used to identify the type of input token encountered when parsing the input stream. The ttype variable is set either to one of these constants or to a single character based on the kind of token that is read from the input stream. The TT_ constants are used to indicate a number, word, end of line, or end of file. When a word token is read, the actual word is stored in the sval variable and ttype is set to TT_WORD. When a number token is read, its value is stored in the nval variable and ttype is set to TT_NUMBER. When other special characters, such as @ or *, are read from the input stream, they are assigned directly to the ttype variable.
The StreamTokenizer constructor takes a Reader object as an argument and generates a StreamTokenizer object. The StreamTokenizer access methods can be divided into two groups: parser parameter-definition methods and stream-processing methods.
The parser parameter-definition methods are used to control the operation of the parser. The commentChar(), slashSlashComments(), and slashStarComments() methods are used to define comments. Comments are ignored by the parser. The whitespaceChars(), wordChars(), quoteChar(), ordinaryChar(), and ordinaryChars() methods are used to set the parser's token-generation parameters. The parseNumbers() and eolIsSignificant() methods toggle number and end-of-line parsing. The lowerCaseMode() method controls whether input words are converted to lowercase, and the resetSyntax() method is used to reset the syntax table, causing all characters to be treated as special characters.
The stream-processing methods are used to read tokens from the input stream, push tokens back out onto the input stream, and return the current line number associated with the input stream. The nextToken() method is used to get the next token from the input stream. The pushBack() method pushes the current token back out onto the input stream. The lineno() method returns the current line number associated with the input stream.
The toString() method of class Object is overwritten to allow printing of the current token.
The StreamTokenApp program demonstrates the ease with which StreamTokenizer can be used to create a parser. This program reads input from the standard input stream, parses input tokens, and displays the token type and value to the console window (see Listing 17.15).
import java.lang.System; import java.io.StreamTokenizer; import java.io.InputStreamReader; import java.io.BufferedReader; import java.io.IOException; public class StreamTokenApp { public static void main(String args[]) throws IOException { BufferedReader inData = new BufferedReader(new InputStreamReader(System.in)); StreamTokenizer inStream = new StreamTokenizer(inData); inStream.commentChar(`#'); boolean eof = false; do { int token=inStream.nextToken(); // Parse according to input token switch(token){ case inStream.TT_EOF: System.out.println("EOF encountered."); eof = true; break; case inStream.TT_EOL: System.out.println("EOL encountered."); break; case inStream.TT_WORD: System.out.println("Word: "+inStream.sval); break; case inStream.TT_NUMBER: System.out.println("Number: "+inStream.nval); break; default: System.out.println((char) token+" encountered."); if(token=='!') eof=true; } } while(!eof); } }
The program creates a new object of class BufferedReader using System.in as an argument. It then converts the BufferedReader object into a StreamTokenizer object and assigns it to the inStream variable. It then sets the comment-line character to #.
Having set up the parser, StreamTokenApp reads tokens from inStream until the end of file is encountered. It uses a switch statement to identify the type and value of each token read.
The following is an example of the output produced by StreamTokenizer. Try running it with different input lines. An exclamation point (!) is used to terminate the program's execution:
This is a test. Word: This Word: is Word: a Word: test. 123 456 Number: 123.0 Number: 456.0 12.34 56.78 Number: 12.34 Number: 56.78 @ $ % ^ @ encountered. $ encountered. % encountered. ^ encountered. #This is a comment This is #a comment Word: This Word: is !
© Copyright, Macmillan Computer Publishing. All rights reserved.