前面咱们已经大体分析了经常使用的字节流,如今咱们来经过分析两个抽象类Reader和Writer来了解下字符流。java
根据JDK源码的注释Reader是字符输入流的抽象接口,它的子类必须实现的方法仅有read(char[], int off, int len)、close()方法,大部分子类都会选择重写某些方法以提供更好的性能或者额外的功能,例如BufferedReader等。子类能够经过重写mark、markSuppot()和reset方法支持输入流标记的功能。数组
如下是该类的源码解读:缓存
package java.io; public abstract class Reader implements Readable, Closeable { //对象local主要用于同步针对此流的操做,处于性能考虑,一个字符流可能会使用一个内部的锁对象保护关键代码 //块(同步代码块),JDK推荐使用此对象而不是使用当前对象this或者使用synchronized方法 protected Object lock; //构造函数,建立一个字符流Reader,它的同步代码块将使用自己this对象进行同步 protected Reader() { this.lock = this; } //构造函数,它的同步代码块将使用方法指定的lock对象进行同步 protected Reader(Object lock) { if (lock == null) { throw new NullPointerException(); } this.lock = lock; } /** * 尝试读取字符到指定的字符缓冲区 * 缓冲区可用做字符的缓冲区,所作的惟一改变是put操做的结果,不对缓冲区执行翻转或者重绕操做 */ public int read(java.nio.CharBuffer target) throws IOException { //获取缓冲区的剩余字符个数 int len = target.remaining(); char[] cbuf = new char[len]; //从输入流中读取len个字符到字符数组cbuf中,返回实际读取的字符数 int n = read(cbuf, 0, len); //若实际读取的字符数大于0,将实际读取到的字符存入缓冲区target中 if (n > 0) target.put(cbuf, 0, n); //返回实际读取的字符个数 return n; } /** * Reads a single character. This method will block until a character is * available, an I/O error occurs, or the end of the stream is reached. * * <p> Subclasses that intend to support efficient single-character input * should override this method. * * @return The character read, as an integer in the range 0 to 65535 * (<tt>0x00-0xffff</tt>), or -1 if the end of the stream has * been reached * * @exception IOException If an I/O error occurs */ public int read() throws IOException { char cb[] = new char[1]; if (read(cb, 0, 1) == -1) return -1; else return cb[0]; } /** * 将字符输入流中的数据读入到cbuf,该方法会阻塞直到存在新的可读数据,发生IO错误以及到达读取终点 * 返回实际读取的字符数,或者-1标识已经到达字符输入流末尾 */ public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); } /** * 从输入流中读取字符数据到指定的字符数组cbuf,方法指定了数组cbuf存储输入流字符数据的起点、和读取的最 * 大字符数,返回实际读取的字符个数,-1标识到达输入流末尾 */ abstract public int read(char cbuf[], int off, int len) throws IOException; /** 保存被跳过字符数据缓存数组长度的最大限制 */ private static final int maxSkipBufferSize = 8192; /** 保存被跳过字符数据的缓存数组 ,初始化为null须要的时候进行分配 **/ private char skipBuffer[] = null; /** * 跳过指定字符数. 该方法可能阻塞当跳过的字符中有些字符正在使用,或者发生IO错误,或者输入流读取结束 */ public long skip(long n) throws IOException { //n小于0抛出IllagalArgumentException非法参数异常 if (n < 0L) throw new IllegalArgumentException("skip value is negative"); //跳转字符数不能超过最大限制,JDK1.8规定是8192 int nn = (int) Math.min(n, maxSkipBufferSize); synchronized (lock) { //若现有保存跳转字符的缓冲区数组skipBuffer为空或者长度小于指定跳转字符数则为他从新分配一个 //长度为指定跳转字符数大小的char数组 if ((skipBuffer == null) || (skipBuffer.length < nn)) skipBuffer = new char[nn]; long r = n; while (r > 0) { int nc = read(skipBuffer, 0, (int)Math.min(r, nn)); if (nc == -1) break; r -= nc; } return n - r; } } /** * 检测当前输入流是否可读 * 若是下一次read不会被阻塞则返回true不然返回false,注意返回false也不是说下次read必定阻塞 */ public boolean ready() throws IOException { return false; } /** * 返回本类是否支持标记,支持返回true不支持返回false,默认实现返回false,子类若是支持标记须要重写该 *方法 */ public boolean markSupported() { return false; } /** * 标记输入流的当前位置.以后调用reset将会重定位到当前位置从新开始读取.不是全部的字符输入流都支持mark * 方法,readAheadLimit参数用于指定标记以后到能够调用reset方法重置输入流的字符数,当上一次标记位置 * 以后继续读取超过该限制的字符数据尝试经过调用reset回滚到上次标记位置从新读取将会失败 * 发生IO错误或者该字符输入流不支持mark操做方法抛出IOException异常 */ public void mark(int readAheadLimit) throws IOException { throw new IOException("mark() not supported"); } /** * 重置输入流. 若是该输入流被标记, 那么尝试从新定位到以前标记的位置从新读取以后的数据. * 若是输入流没有标记过,那么根据不一样的实现类,会进行不一样的重置处理,例如重定位当前读取位置到输入流初始 * 位置.并非全部的字符输入流都支持reset方法,一些支持reset操做的可能也不支持标记操做mark. * 当输入流不曾标记或者标记无效或者输入流不支持reset方法或者发生其余的IO错误都会抛出IO异常 */ public void reset() throws IOException { throw new IOException("reset() not supported"); } /** * 关闭输入流释放与之相关的系统资源,当输入流关闭以后,调用read、ready、mark、reset或者skip方法都 *将抛出IOException异常,调用close方法关闭以前已经关闭的输入流不会有任何影响。 */ abstract public void close() throws IOException; }
Writer是字符输出流的抽象接口,它的子类必须实现的方法包括write(char[], int off, int len),flush()和close()方法。大部分子类都会选择重写某些方法已提供更好的性能或者额外的方法。app
如下是该类的源码解读:ide
package java.io; public abstract class Writer implements Appendable, Closeable, Flushable { /** * 字符缓冲数组用于临时存放将要写入到输出流中的字符 */ private char[] writeBuffer; /** * 字符缓冲数组的容量 */ private static final int WRITE_BUFFER_SIZE = 1024; /** * 锁对象用于同步针对该输出流的操做.处于性能考虑,字符输出流对象可能会使用一个锁对象而不是对它自己加锁去保护关键 * 代码块(同步代码块)。子类应该使用这个锁对象而不是Writer对象自己或者同步方法 */ protected Object lock; /** * 构造函数。它关键部分的代码(须要同步的代码块)将会选择他自己做为锁对象进行同步 */ protected Writer() { this.lock = this; } /** * 构造函数。它的关键部分代码(同步代码块)将使用方法指定的锁对象进行同步 */ protected Writer(Object lock) { if (lock == null) { throw new NullPointerException(); } this.lock = lock; } /** * 写入单个字符,要写入的字符包含在给定整数值的低16位中,高16位被忽略 * 子类若想实现高效的单字符写入方法可重写 */ 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); } } /** * 尝试将一个字节数组写入到输出流中 */ public void write(char cbuf[]) throws IOException { write(cbuf, 0, cbuf.length); } /** * 尝试将一个数组从off开始的len个字符写入到输出流中,可能写入的字符数小于len个 */ abstract public void write(char cbuf[], int off, int len) throws IOException; /** * 写入一个字符串 */ public void write(String str) throws IOException { write(str, 0, str.length()); } /** * 试图将字符串的一部分,从off开始的len个字符写入到输出流中 * 尝试写入len个字符,但写入的字符数可能低于len个 */ 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); } } /** * 将指定的字符序列写入到字符输出流末尾。与调用out.write的效果彻底相同。由于调用该方法实际写入输出流的内容实际上是 * csq.toString方法返回的内容,因此整个字符序列可否被所有被写进去取决于指定字符序列csq的toString方法实现, * 例如调用一个CharacterBuffer的toString方法,方法返回的内容取决于它当前的读取位置和保存的字符个数。当csq为 * null时会往输入流写入“null”四个字符 */ public Writer append(CharSequence csq) throws IOException { if (csq == null) write("null"); else write(csq.toString()); return this; } /** * 将指定字符序列的一部分(start位置开始到end位置不包括end位置字符)追加到该字符输出流末尾, */ 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; } /** * 往输出流追加指定的字符 */ public Writer append(char c) throws IOException { write(c); return this; } /** * 刷新该流的缓冲区.若是缓冲数组保存了write方法写入的字符数据,那么马上将他们写入到目标对象,目标对象多是绑定的 * 另外一个字符输出流或者字节输出流,该方法将会刷新绑定的全部字符和字节输出流的缓冲区 * 若是写入的目标是一个File对象那么该输出流仅保证先前写入流的数据传递到操做系统进行写入,可是不保证当即写入物理设 * 备中 */ abstract public void flush() throws IOException; /** * 关闭当前输出流方法,抽象方法留给子类实现, 建议关闭流以前先调用刷新函数flush刷新缓存.一旦该输出流关闭后续调用 * write和flush方法都应该抛出一个IO异常。重复关闭一个字符输出流不会有问题 */ abstract public void close() throws IOException; }
1)操做的对象不一样,Reader操做的是字符,InputStream是字节;函数
2)Reader默认实现了一个Readable接口,比InputStream多提供了一个将输入流中的字符数据读取到指定字符缓存CharBuffer的方法(read(java.nio.CharBuffer));性能
3)Reader的close方法是抽象的子类必须实现,而InputStream的close方法不是。this
1)Write操做的是字符,OutputStream操做的是字节;spa
2)实现的接口不一样,Writer相比OutputStream多实现了Appendable接口,提供了几个在输出流中写入单个字符、字符序列的方法;操作系统
3)Writer的close、flush方法是抽象的,子类必须实现,而OutputStream的close、flush方法不是;