前面我们讲了InputStream和OutputStream,以及与它们相关的FilterInputStream、FilterOutputStream,用来提高读写效率的BufferedInputStream、BuffedOutputStream,这些都是跟字节打交道的,但是我们平时处理文本文件或者进行网络请求,一般都需要直接处理字符,所以从这篇文章开始我们来学习跟字符相关的IO类。这其中最为基础的就是Writer和Reader。
Writer
类定义
writer是用来写入字符流的抽象类。
public abstract class Writer implements Appendable, Closeable, Flushable {}
它实现了Appendable、Closebale和Flushable这三个接口。
重要字段
writer有两个字段我们需要先了解:
/**
* Temporary buffer used to hold writes of strings and single characters
*/
private char[] writeBuffer;
/**
* Size of writeBuffer, must be >= 1
*/
private static final int WRITE_BUFFER_SIZE = 1024;
第一个writeBuffer作为临时的缓存字符数组,第二个WRITE_BUFFE_SIZE是缓存数组的大小。一会我们会看到缓存字符数组的用处。
方法
单字符写入方法
writer这个抽象类最重要的方法就是write方法,不过它有五个参数不同的write方法,我们一个一个看。
第一个是写入单字符的write方法:
public void write(int c) throws IOException {
synchronized (lock) {
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
首先一个问题,为什么要写入的是字符,而参数类型是int呢?因为java中的char类型是两个字节也就是16位大小的,而int是4字节32位大小的,所以int能完全包含char,那么我们要写入4个字节中的哪两个字节呢?这个方法的注释中给出了解释:
写入的是低位的两个字节。
我们继续来看逻辑,首先判断writeBuffer也就是上面说的缓存字符数组是否为空,为空的话首先初始化这个字符数组(注意这个方法里面的锁,保证了writeBuffer只会初始化一次),在整个writer类的逻辑中,这个缓存数组都是在write方法中来判断并进行初始化的,并没有在构造方法中初始化,也就是外部完全不知道这个缓存字符数组的存在。然后对于这个写入的int,处理方式是将它作为writeBuffer的第一个元素,然后调用write(char cbuf[],int off,int len)方法来执行写入。而write(char cbuf[],int off,int len)这个方法是一个抽象方法,是子类需要给出实现的:
abstract public void write(char cbuf[], int off, int len) throws IOException;
从现在来看,缓存字符数组与我们之前学到的BufferedOutputStream中的缓存字节数组的功能并不相同,BufferedOutputStream中的字节数组用来对多次的write方法调用中的字节进行累积并保存然后一次性进行写入来提高效率,而Writer中的writeBuffer并没有累积,而是每次调用仅保存一下就直接写入,那这样做有什么意义呢?
多字节写入方法
多字节写入的write方法参数是一个字符数组:
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
字符串写入方法
字符串写入的write方法的参数是一个字符串:
public void write(String str) throws IOException {
write(str, 0, str.length());
}
它调用的是另外一个写入字符串的方法:
public void write(String str, int off, int len) throws IOException {
synchronized (lock) {
char cbuf[];
if (len <= WRITE_BUFFER_SIZE) {
if (writeBuffer == null) {
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
cbuf = writeBuffer;
} else { // Don't permanently allocate very large buffers.
cbuf = new char[len];
}
str.getChars(off, (off + len), cbuf, 0);
write(cbuf, 0, len);
}
}
它的逻辑也不复杂,就是把要写入的字符串中的字符给到一个字符数组cbuf,如果要写入的字符的长度小于等于默认的字符数组的长度,那么cbuf其实就是writeBuffer,然后同样是调用write(cbuf, 0, cbuf.length)来执行最终的写入。
所以不管是单字符、多字符还是字符串的写入,最后都是通过下面这个方法来实现写入的:
abstract public void write(char cbuf[], int off, int len) throws IOException;
Reader
类定义
public abstract class Reader implements Readable, Closeable{}
Reader实现了Readable和Closeable接口。
方法
Reader提供了很多read方法,子类需要给出下面多字符read方法的实现:
abstract public int read(char cbuf[], int off, int len) throws IOException;
单字符read方法
public int read() throws IOException {
char cb[] = new char[1];
if (read(cb, 0, 1) == -1)
return -1;
else
return cb[0];
}
它首先声明了一个字符数组cb,然后调用的是抽象的read方法。
多字符read方法
public int read(char cbuf[]) throws IOException {
return read(cbuf, 0, cbuf.length);
}
它也是直接调用抽象的多字符read方法来实现的。
与InputStream、OutputStream的对比
InputStream和OutputStream中对单字节的read、write方法是抽象的,需要子类给出实现,其他的都是通过调用单字节的read、write方法来实现的。而Reader和Writer中都是多字符的read、write方法是抽象的,需要子类给出实现,而单字符的read、write方法是通过调用多字符方法来实现的。