Readers and Writers

In Java 1.1 and later, streams are primarily intended for data that can be read as pure bytes—basically byte data and numeric data encoded as binary numbers of one sort or another. Streams are specifically not intended for use when reading and writing text, including both ASCII text, like “Hello World,” and numbers formatted as text, like “3.1415929.” For these purposes, you should use readers and writers.

Input and output streams are fundamentally byte-based. Readers and writers are based on characters, which can have varying widths depending on the character set. For example, ASCII and ISO Latin-1 use one-byte characters. Unicode uses two-byte characters. UTF-8 uses characters of varying width (between one and three bytes). Since characters are ultimately composed of bytes, readers take their input from streams. However, they convert those bytes into chars according to a specified encoding format before passing them along. Similarly, writers convert chars to bytes according to a specified encoding before writing them onto some underlying stream.

The java.io.Reader and java.io.Writer classes are abstract superclasses for classes that read and write character-based data. The subclasses are notable for handling the conversion between different character sets. There are nine reader and eight writer classes in the core Java API, all in the java.io package:

BufferedReader

BufferedWriter

CharArrayReader

CharArrayWriter

FileReader

FileWriter

FilterReader

FilterWriter

InputStreamReader

LineNumberReader

OutputStreamWriter

PipedReader

PipedWriter

PrintWriter

PushbackReader

StringReader

StringWriter

 

For the most part, these classes have methods that are extremely similar to the equivalent stream classes. Often the only difference is that a byte in the signature of a stream method is replaced by a char in the signature of the matching reader or writer method. For example, the java.io.OutputStream class declares these three write() methods:

public abstract void write(int i) throws IOException
public void write(byte[] data) throws IOException
public void write(byte[] data, int offset, int length) throws IOException

The java.io.Writer class, therefore, declares these three write() methods:

public void write(int i) throws IOException
public void write(char[] data) throws IOException
public abstract void write(char[] data, int offset, int length) throws IOException

As you can see, the six signatures are identical except that in the latter two methods the byte array data has changed to a char array. There’s also a less obvious difference not reflected in the signature. While the int passed to the OutputStream write() method is reduced modulo 256 before being output, the int passed to the Writer write() method is reduced modulo 65,536. This reflects the different ranges of chars and bytes.

java.io.Writer also has two more write() methods that take their data from a string:

public void write(String s) throws IOException
public void write(String s, int offset, int length) throws IOException

Because streams don’t know how to deal with character-based data, there are no corresponding methods in the java.io.OutputStream class.

Get Java I/O now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.