20.3 Character Streams
The abstract classes for reading and writing streams of characters are Reader and Writer. Each supports methods similar to those of its byte stream counterpart—InputStream and OutputStream, respectively. For example, InputStream has a read method that returns a byte as the lowest 8 bits of an int, and Reader has a read method that returns a char as the lowest 16 bits of an int. And where OutputStream has methods that write byte arrays, Writer has methods that write char arrays. The character streams were designed after the byte streams to provide full support for working with Unicode characters, and in the process the contracts of the classes were improved to make them easier to work with. The type tree for the character streams of java.io appears in Figure 20-2.
Figure 20-2 Type Tree for Character Streams in java.io
As with the byte streams, character streams should be explicitly closed to release resources associated with the stream. Character stream synchronization policies are discussed in Section 20.5.1 on page 515.
20.3.1 Reader
The abstract class Reader provides a character stream analogous to the byte stream InputStream and the methods of Reader essentially mirror those of InputStream:
-
public int
read()
throws IOException
- Reads a single character and returns it as an integer in the range 0 to 65535. If no character is available because the end of the stream has been reached, the value –1 is returned. This method blocks until input is available, the end of stream is found, or an exception is thrown.
-
public abstract int
read(char[] buf, int offset, int count)
throws IOException
- Reads into a part of a char array. The maximum number of characters to read is count. The read characters are stored from buf[offset] up to a maximum of buf[offset+count-1]—all other values in buf are left unchanged. The number of characters actually read is returned. If no characters are read because the end of the stream was found, –1 is returned. If count is zero then no characters are read and zero is returned. This method blocks until input is available, the end of stream is found, or an exception is thrown. If the first character cannot be read for any reason other than finding the end of the stream—in particular, if the stream has already been closed—an IOException is thrown. Once a character has been read, any failure that occurs while trying to read characters does not cause an exception, but is treated just like finding the end of the stream—the method completes normally and returns the number of characters read before the failure occurred.
-
public int
read(char[] buf)
throws IOException
- Equivalent to read(buf,0, buf.length).
-
public int
read(java.nio.CharBuffer buf)
throws IOException
- Attempts to read as many characters as possible into the specified character buffer, without overflowing it. The number of characters actually read is returned. If no characters are read because the end of the stream was found, –1 is returned. This is equivalent to reading into an array that has the same length as the buffer has available capacity, and then copying the array into the buffer. This method is defined in the java.lang.Readable interface, and has no counterpart in InputStream.
-
public long
skip(long count)
throws IOException
- Skips as many as count characters of input or until the end of the stream is found. Returns the actual number of characters skipped. The value of count must not be negative.
-
public boolean
ready()
throws IOException
- Returns true if the stream is ready to read; that is, there is at least one character available to be read. Note that a return value of false does not guarantee that the next invocation of read will block because data could have become available by the time the invocation occurs.
-
public abstract void
close()
throws IOException
- Closes the stream. This method should be invoked to release any resources (such as file descriptors) associated with the stream. Once a stream has been closed, further operations on the stream will throw an IOException. Closing a previously closed stream has no effect.
The implementation of Reader requires that a subclass provide an implementation of both the read method that reads into a char array, and the close method. Many subclasses will be able to improve performance if they also override some of the other methods.
There are a number of differences between Reader and InputStream. With Reader the fundamental reading method reads into a char array and the other read methods are defined in terms of this method. In contrast the InputStream class uses the single-byte read method as its fundamental reading method. In the Reader class subclasses must implement the abstract close method in contrast to inheriting an empty implementation—many stream classes will at least need to track whether or not they have been closed and so close will usually need to be overridden. Finally, where InputStream had an available method to tell you how much data was available to read, Reader simply has a ready method that tells you if there is any data.
As an example, the following program counts the number of whitespace characters in a character stream:
import java.io.*; class CountSpace { public static void main(String[] args) throws IOException { Reader in; if (args.length == 0) in = new InputStreamReader(System.in); else in = new FileReader(args[0]); int ch; int total; int spaces = 0; for (total = 0; (ch = in.read()) != -1; total++) { if (Character.isWhitespace((char) ch)) spaces++; } System.out.println(total + " chars, " + spaces + " spaces"); } }
This program takes a filename from the command line. The variable in represents the character stream. If a filename is not provided, the standard input stream, System.in, is used after wrapping it in an InputStreamReader, which converts an input byte stream into an input character stream; if a filename is provided, an object of type FileReader is created, which is a subclass of Reader.
The for loop counts the total number of characters in the file and the number of spaces, using the Character class's isWhitespace method to test whether a character is whitespace. At the end, the results are printed. Here is the output of the program when used on itself:
453 chars, 111 spaces
20.3.2 Writer
The abstract class Writer provides a stream analogous to OutputStream but designed for use with characters instead of bytes. The methods of Writer essentially mirror those of OutputStream, but add some other useful forms of write:
-
public void
write(int ch)
throws IOException
- Writes ch as a character. The character is passed as an int but only the lowest 16 bits of the integer are written. This method blocks until the character is written.
-
public abstract void
write(char[] buf, int offset, int count)
throws IOException
- Writes part of an array of characters, starting at buf[offset] and writing count characters. This method blocks until the characters have been written.
-
public void
write(char[] buf)
throws IOException
- Equivalent to write(buf,0, buf.length).
-
public void
write(String str, int offset, int count)
throws IOException
- Writes count characters from the string str onto the stream, starting with str.charAt(offset).
-
public void
write(String str)
throws IOException
- Equivalent to write(str,0, str.length()).
-
public abstract void
flush()
throws IOException
- Flushes the stream. If the stream has buffered any characters from the various write methods, flush immediately writes them to their destination. Then, if that destination is another stream, it is also flushed. One flush invocation will flush all the buffers in a chain of streams. If a stream is not buffered flush will do nothing.
-
public abstract void
close()
throws IOException
- Closes the stream, flushing if necessary. This method should be invoked to release any resources (such as file descriptors) associated with the stream. Once a stream has been closed, further operations on the stream will throw an IOException. Closing a previously closed stream has no effect.
Subclasses of Writer must implement the array writing variant of write, the close method, and the flush method. All other Writer methods are implemented in terms of these three. This contrasts with OutputStream which uses the single-byte variant of write method as the fundamental writing method, and which provides default implementations of flush and close. As with Reader, many subclasses can improve performance if they also override other methods.
Writer also implements the java.lang.Appendable interface—see page 332. The append(charc) method is equivalent to write(c); the append methods that take a CharSequence are equivalent to passing the String representations of the CharSequence objects to the write(Stringstr) method.
20.3.3 Character Streams and the Standard Streams
The standard streams System.in, System.out, and System.err existed before the character streams were invented, so these streams are byte streams even though logically they should be character streams. This situation creates some anomalies. It is impossible, for example, to replace System.in with a LineNumberReader to keep track of the standard input stream's current line number. By attaching an InputStreamReader—an object that converts a byte input stream to a character input stream—to System.in, you can create a LineNumberReader object to keep track of the current line number (see "LineNumberReader" on page 527). But System.in is an InputStream, so you cannot replace it with a LineNumberReader, which is a type of Reader, not an InputStream.
System.out and System.err are PrintStream objects. PrintStream has been replaced by its equivalent character-based version PrintWriter. Generally, you should avoid creating PrintStream objects directly. You'll learn about the Print stream classes in Section 20.5.8 on page 525.