Additional java.io Classes
We now introduce you to other useful classes in the java.io package. We overview additional interfaces and classes for byte-based input and output streams, and character-based input and output streams.
Interfaces and Classes for Byte-Based Input and Output
InputStream and OutputStream (subclasses of Object) are abstract classes that declare methods for performing byte-based input and output, respectively. We used concrete classes FileInputStream (a subclass of InputStream) and FileOutputStream (a subclass of OutputStream) to manipulate files in this chapter.
Pipes are synchronized communication channels between threads. We discuss threads in Chapter 23, Multithreading. Java provides PipedOutputStream (a subclass of OutputStream) and PipedInputStream (a subclass of InputStream) to establish pipes between two threads in a program. One thread sends data to another by writing to a PipedOutputStream. The target thread reads information from the pipe via a PipedInputStream.
A FilterInputStream filters an InputStream, and a FilterOutputStream filters an OutputStream. Filtering means simply that the filter stream provides additional functionality, such as aggregating data bytes into meaningful primitive-type units. FilterInputStream and FilterOutputStream are abstract classes, so some of their filtering capabilities are provided by their concrete subclasses.
A PrintStream (a subclass of FilterOutputStream) performs text output to the specified stream. Actually, we have been using PrintStream output throughout the text to this pointSystem.out and System.err are PrintStream objects.
Reading data as raw bytes is fast, but crude. Usually, programs read data as aggregates of bytes that form ints, floats, doubles and so on. Java programs can use several classes to input and output data in aggregate form.
Interface DataInput (discussed in Section 14.7.1) describes methods for reading primitive types from an input stream. Classes DataInputStream and RandomAccessFile each implement this interface to read sets of bytes and view them as primitive-type values. Interface DataInput includes methods readLine (for byte arrays), readBoolean, readByte, readChar, readDouble, readFloat, readFully (for byte arrays), readInt, readLong, readShort, readUnsignedByte, readUnsignedShort, readUTF (for reading Unicode characters encoded by Javawe discuss UTF encoding in Appendix F, Unicode®) and skipBytes.
Interface DataOutput (discussed in Section 14.7.1) describes a set of methods for writing primitive types to an output stream. Classes DataOutputStream (a subclass of FilterOutputStream) and RandomAccessFile each implement this interface to write primitive-type values as bytes. Interface DataOutput includes overloaded versions of method write (for a byte or for a byte array) and methods writeBoolean, writeByte, writeBytes, writeChar, writeChars (for Unicode Strings), writeDouble, writeFloat, writeInt, writeLong, writeShort and writeUTF (to output text modified for Unicode).
Buffering is an I/O-performance-enhancement technique. With a BufferedOutputStream (a subclass of class FilterOutputStream), each output statement does not necessarily result in an actual physical transfer of data to the output device (which is a slow operation compared to processor and main memory speeds). Rather, each output operation is directed to a region in memory called a buffer that is large enough to hold the data of many output operations. Then, actual transfer to the output device is performed in one large physical output operation each time the buffer fills. The output operations directed to the output buffer in memory are often called logical output operations. With a BufferedOutputStream, a partially filled buffer can be forced out to the device at any time by invoking the stream object's flush method.
Using buffering can greatly increase the efficiency of an application. Typical I/O operations are extremely slow compared with the speed of accessing computer memory. Buffering reduces the number of I/O operations by first combining smaller outputs together in memory. The number of actual physical I/O operations is small compared with the number of I/O requests issued by the program. Thus, the program that is using buffering is more efficient.
Performance Tip 14.1
Buffered I/O can yield significant performance improvements over unbuffered I/O. |
With a BufferedInputStream (a subclass of class FilterInputStream), many "logical" chunks of data from a file are read as one large physical input operation into a memory buffer. As a program requests each new chunk of data, it is taken from the buffer. (This procedure is sometimes referred to as a logical input operation.) When the buffer is empty, the next actual physical input operation from the input device is performed to read in the next group of "logical" chunks of data. Thus, the number of actual physical input operations is small compared with the number of read requests issued by the program.
Earlier in the chapter we used class StringBuffer, which allows us to dynamically manipulate strings. It is important to note that class StringBuffer can be used to buffer output that will be displayed later to the screen or in a JTextArea. This increases the efficiency of a programjust as with buffering, it is much faster to first combine all the program's output in a StringBuffer object and display the final output to a JTextArea or the screen, then to continually add text to a JTextArea or the screen. We discuss class StringBuffer in more detail in Chapter 29, Strings, Characters and Regular Expressions.
Java stream I/O includes capabilities for inputting from byte arrays in memory and outputting to byte arrays in memory. A ByteArrayInputStream (a subclass of InputStream) reads from a byte array in memory. A ByteArrayOutputStream (a subclass of OutputStream) outputs to a byte array in memory. One use of byte-array I/O is data validation. A program can input an entire line at a time from the input stream into a byte array. Then a validation routine can scrutinize the contents of the byte array and correct the data if necessary. Finally, the program can proceed to input from the byte array, "knowing" that the input data is in the proper format. Outputting to a byte array is a nice way to take advantage of the powerful output-formatting capabilities of Java streams. For example, data can be stored in a byte array, using the same formatting that will be displayed at a later time, and the byte array can then be output to a disk file to preserve the screen image.
A SequenceInputStream (a subclass of InputStream) enables concatenation of several InputStreams, which means that the program sees the group as one continuous InputStream. When the program reaches the end of an input stream, that stream closes, and the next stream in the sequence opens.
Interfaces and Classes for Character-Based Input and Output
In addition to the byte-based streams, Java provides the Reader and Writer abstract classes, which are Unicode two-byte, character-based streams. Most of the byte-based streams have corresponding character-based concrete Reader or Writer classes.
Classes BufferedReader (a subclass of abstract class Reader) and BufferedWriter (a subclass of abstract class Writer) enable buffering for character-based streams. Remember that character-based streams use Unicode characterssuch streams can process data in any language that the Unicode character set represents.
Classes CharArrayReader and CharArrayWriter read and write, respectively, a stream of characters to a character array. A LineNumberReader (a subclass of BufferedReader) is a buffered character stream that keeps track of the number of lines read (i.e., a newline, a return or a carriage-returnline-feed combination). Keeping track of line numbers can be useful if the program needs to inform the reader of an error on a specific line.
Class FileReader (a subclass of InputStreamReader) and class FileWriter (a subclass of OutputStreamWriter) read characters from and write characters to a file, respectively. Class PipedReader and class PipedWriter implement piped-character streams that can be used to transfer information between threads. Class StringReader and StringWriter read characters from and write characters to Strings, respectively. A PrintWriter writes characters to a stream.