设计Reader和Writer继承层次结构主要是为了国际化。老的IO流继承层次结构仅支持8位的字节流,并且不能很好的处理16位的Unicode字符,所以添加Reader和Writer继承层次结构就是为了在所有的IO操作中都支持Unicode。
然而在某些场合,我们不得不面临着字符编码的问题,即字符和字节之间按照什么编码方式(GBK,UTF-8,ISO-8859-1等)来编解码的问题。这时我们将用到OutputStreamWriter和InputStreamReader这两个字符流,它们分别是字符通向字节的桥梁和字节通向字符的桥梁。
关于“装饰者模式”,字符流Reader和Writer的类继承层次结构沿用与字节流相同的思想,但是并不完全相同。造成这种差异的原因是因为类的组织形式不同,尽管BufferedOutputStream是FilterOutputStream的子类,但是BufferedWriter并不是FilterWriter的子类,但它仍然是其他Writer类的装饰器类。尽管FilterWriter是抽象类,但是没有任何子类,把它放在那里也只是把它作为一个占位符,或者仅仅让我们不会对它所在的地方产生疑惑。
下图是字符流的继承体系结构:
一,Writer源码:
package java.io;
/**
* Writer是写入字符流的抽象类。定义了流的最基本的功能。
* 子类必须实现的方法仅有 write(char[], int, int)、flush() 和 close()。
* 但是,多数子类将重写此处定义的一些方法,以提供更高的效率和/或其他功能。
*/
public abstract class Writer implements Appendable, Closeable, Flushable {
/**
* 字符buffer
*/
private char[] writeBuffer;
/**
* 字符buffer的默认大小
*/
private static final int WRITE_BUFFER_SIZE = 1024;
/**
* 用于同步针对此流的操作的对象
*/
protected Object lock;
/**
* 构造方法1,使用本类对象锁
*/
protected Writer() {
this.lock = this;
}
/**
* 构造方法2,使用指定的锁对象
*/
protected Writer(Object lock) {
if (lock == null) {
throw new NullPointerException();
}
this.lock = lock;
}
/**
* 写入单个字符。要写入的字符包含在给定整数值的 16 个低位中,16 高位被忽略。
* 用于支持高效单字符输出的子类应重写此方法。
*/
public void write(int c) throws IOException {
synchronized (lock) { // write()方法内是同步代码块
if (writeBuffer == null){
writeBuffer = new char[WRITE_BUFFER_SIZE];
}
writeBuffer[0] = (char) c;
write(writeBuffer, 0, 1);
}
}
/**
* 写入字符数组。
*/
public void write(char cbuf[]) throws IOException {
write(cbuf, 0, cbuf.length);
}
/**
* 写入字符数组的某一部分。子类需实现该方法
*/
abstract public void write(char cbuf[], int off, int len) throws IOException;
/**
* 写入字符串。
*/
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);
}
}
/**
* 将指定字符序列添加到此 writer
*/
public Writer append(CharSequence csq) throws IOException {
if (csq == null)
write("null");
else
write(csq.toString());
return this;
}
/**
* 将指定字符序列的子序列添加到此 writer。
*/
public Writer append(CharSequence csq, int start, int end) throws IOException {
CharSequence cs = (csq == null ? "null" : csq);
write(cs.subSequence(start, end).toString());
return this;
}
/**
* 将指定字符添加到此 writer。
*/
public Writer append(char c) throws IOException {
write(c);
return this;
}
/**
* 刷新该流的缓冲。
*/
abstract public void flush() throws IOException;
/**
* 关闭此流,但要先刷新它。
* 在关闭该流之后,再调用 write() 或 flush() 将导致抛出 IOException。
*/
abstract public void close() throws IOException;
}
二,OutputStreamWriter源码
package java.io;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import sun.nio.cs.StreamEncoder;
/**
* OutputStreamWriter是字符流通向字节流的桥梁:可使用指定的charset将要写入流中的字符编码成字节。
* 它使用的字符集可以由名称指定或显式给定,否则将接受平台默认的字符集。
* 每次调用 write() 方法都会导致在给定字符(或字符集)上调用编码转换器。
* 在写入底层输出流之前,得到的这些字节将在缓冲区中累积。
* 为了获得最高效率,可考虑将OutputStreamWriter包装到BufferedWriter中,以避免频繁调用转换器。
* 例如:Writer out = new BufferedWriter(new OutputStreamWriter(System.out));
*/
public class OutputStreamWriter extends Writer {
/**
* 本类所实现的由字符到字节的编码严重依赖StreamEncoder类及其方法。
* 本文并不打算讲解StreamEncoder类,只介绍其方法达到什么目的。
*/
private final StreamEncoder se;
/**
* 创建使用指定字符集的 OutputStreamWriter。
*/
public OutputStreamWriter(OutputStream out, String charsetName)
throws UnsupportedEncodingException
{
super(out);
if (charsetName == null)
throw new NullPointerException("charsetName");
se = StreamEncoder.forOutputStreamWriter(out, this, charsetName);
}
/**
* 创建使用默认字符编码的 OutputStreamWriter
*/
public OutputStreamWriter(OutputStream out) {
super(out);
try {
se = StreamEncoder.forOutputStreamWriter(out, this, (String)null);
} catch (UnsupportedEncodingException e) {
throw new Error(e);
}
}
/**
* 创建使用给定字符集的 OutputStreamWriter。
*/
public OutputStreamWriter(OutputStream out, Charset cs) {
super(out);
if (cs == null)
throw new NullPointerException("charset");
se = StreamEncoder.forOutputStreamWriter(out, this, cs);
}
/**
* 创建使用给定字符集编码器的 OutputStreamWriter。
*/
public OutputStreamWriter(OutputStream out, CharsetEncoder enc) {
super(out);
if (enc == null)
throw new NullPointerException("charset encoder");
se = StreamEncoder.forOutputStreamWriter(out, this, enc);
}
/**
* 返回此流使用的字符编码的名称。
*/
public String getEncoding() {
return se.getEncoding();
}
/**
* 刷新buffer
*/
void flushBuffer() throws IOException {
se.flushBuffer();
}
/**
* 写入单个字符。
*/
public void write(int c) throws IOException {
se.write(c);
}
/**
* 写入字符数组的某一部分。
*/
public void write(char cbuf[], int off, int len) throws IOException {
se.write(cbuf, off, len);
}
/**
* 写入字符串的某一部分。
*/
public void write(String str, int off, int len) throws IOException {
se.write(str, off, len);
}
/**
* 刷新该流的缓冲。
*/
public void flush() throws IOException {
se.flush();
}
/**
* 关闭此流,但要先刷新它。
*/
public void close() throws IOException {
se.close();
}
}
三,FileWriter
该类实现了特定的目的和write形式,同样的类还有:CharArrayWriter,StringWriter。
package java.io;
/**
* 1,用来写入字符文件的便捷类。
* 2,此类的构造方法假定默认字符编码和默认字节缓冲区大小都是可接受的。
* 要自己指定这些值,可以先在 FileOutputStream 上构造一个 OutputStreamWriter。
* 3,FileWriter 用于写入字符流。要写入原始字节流,请考虑使用 FileOutputStream。
*/
public class FileWriter extends OutputStreamWriter {
/**
* 根据给定的文件名构造一个 FileWriter 对象。
* 内部先根据filename构造一个字节输出流,然后调用父类的构造方法,
* 并由StreamEncoder类关联字符流和字节流。
*/
public FileWriter(String fileName) throws IOException {
super(new FileOutputStream(fileName));
}
/**
* 根据给定的文件名以及指示是否附加写入数据的 boolean 值来构造 FileWriter 对象。
* 同样,调用父类的构造方法。
*/
public FileWriter(String fileName, boolean append) throws IOException {
super(new FileOutputStream(fileName, append));
}
/**
* 根据给定的 File 对象构造一个 FileWriter 对象。
* 同样,调用父类的构造方法。
*/
public FileWriter(File file) throws IOException {
super(new FileOutputStream(file));
}
/**
* 根据给定的 File 对象构造一个 FileWriter 对象。
* 如果第二个参数为 true,则将字节写入文件末尾处,而不是写入文件开始处。
* 同样,调用父类的构造方法。
*/
public FileWriter(File file, boolean append) throws IOException {
super(new FileOutputStream(file, append));
}
/**
* 构造与某个文件描述符相关联的 FileWriter 对象。
* 同样,调用父类的构造方法。
*/
public FileWriter(FileDescriptor fd) {
super(new FileOutputStream(fd));
}
// 该类没有自己的方法,均是继承自父类OutputStreamWriter和Writer。
}
四,BufferedWriter源码
该体系最常用的“装饰者”类。装饰者类还有:PrintWriter。
package java.io;
/**
* 将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。
* 可以指定缓冲区的大小,或者接受默认的大小。在大多数情况下,默认值就足够大了。
* 该类提供了 newLine() 方法,它使用平台自己的行分隔符概念。
*
*/
public class BufferedWriter extends Writer {
private Writer out; // 持有父类对象
private char cb[]; // buffer
private int nChars, nextChar;
private static int defaultCharBufferSize = 8192; // 默认buffer大小
/**
* 换行符
*/
private String lineSeparator;
/**
* 创建一个使用默认大小输出缓冲区的缓冲字符输出流。
*/
public BufferedWriter(Writer out) {
this(out, defaultCharBufferSize);
}
/**
* 创建一个使用给定大小输出缓冲区的新缓冲字符输出流。
*/
public BufferedWriter(Writer out, int sz) {
super(out);
if (sz <= 0)
throw new IllegalArgumentException("Buffer size <= 0");
this.out = out;
cb = new char[sz];
nChars = sz;
nextChar = 0;
lineSeparator = java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction("line.separator"));
}
/** 检查流是否打开 */
private void ensureOpen() throws IOException {
if (out == null)
throw new IOException("Stream closed");
}
/**
* 刷新缓冲区。
*/
void flushBuffer() throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar == 0)
return;
out.write(cb, 0, nextChar);
nextChar = 0;
}
}
/**
* 写入单个字符。
* 底层调用了Writer类型的write()方法,但是本类方法为其进行了"装饰",即加入了缓冲区技术。
*/
public void write(int c) throws IOException {
synchronized (lock) {
ensureOpen();
if (nextChar >= nChars)
flushBuffer();
cb[nextChar++] = (char) c;
}
}
/**
* 内部方法
*/
private int min(int a, int b) {
if (a < b) return a;
return b;
}
/**
* 写入字符数组的某一部分。 同样对老write()方法进行了"缓冲区装饰"
*/
public void write(char cbuf[], int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
if ((off < 0) || (off > cbuf.length) || (len < 0) ||
((off + len) > cbuf.length) || ((off + len) < 0)) {
throw new IndexOutOfBoundsException();
} else if (len == 0) {
return;
}
if (len >= nChars) {
/* If the request length exceeds the size of the output buffer,
flush the buffer and then write the data directly. In this
way buffered streams will cascade harmlessly. */
flushBuffer();
out.write(cbuf, off, len);
return;
}
int b = off, t = off + len;
while (b < t) {
int d = min(nChars - nextChar, t - b);
System.arraycopy(cbuf, b, cb, nextChar, d);
b += d;
nextChar += d;
if (nextChar >= nChars)
flushBuffer();
}
}
}
/**
* 写入字符串的某一部分。同样对老write()方法进行了"缓冲区装饰"。
*/
public void write(String s, int off, int len) throws IOException {
synchronized (lock) {
ensureOpen();
int b = off, t = off + len;
while (b < t) {
int d = min(nChars - nextChar, t - b);
s.getChars(b, b + d, cb, nextChar);
b += d;
nextChar += d;
if (nextChar >= nChars)
flushBuffer();
}
}
}
/**
* 写入一个行分隔符。行分隔符字符串由系统属性 line.separator 定义
*/
public void newLine() throws IOException {
write(lineSeparator);
}
/**
* 刷新该流的缓冲。
*/
public void flush() throws IOException {
synchronized (lock) {
flushBuffer();
out.flush();
}
}
/**
* 关闭此流,但要先刷新它。
*/
@SuppressWarnings("try")
public void close() throws IOException {
synchronized (lock) {
if (out == null) {
return;
}
try (Writer w = out) {
flushBuffer();
} finally {
out = null;
cb = null;
}
}
}
}
Writer和Reader体系的实现基于OutputStream和InputStream,并且体系结构相差不大,只是类组织的形式有些差异,可以类比学习。