Reader的源码、FilterReader源码、PushbackReader源码(windows操作系统,JDK8)
、Reader.class源码Reader 是用来读取字符流的装饰器模式中顶层的抽象类与 InputStream字节流不同的是Reader 专门处理字符char字符char在JVM中使用Unicode编码占2个byte主要用于读取和写入中文文本。windows操作系统的JDK8版本中所有的Reader的子类如下此处只展示部分常用的Reader子类有以下5个①、InputStreamReader处理文件的字符输入流在进行文件读操作时如果遇到不同编码格式可以使用 InputStreamReader 进行处理。②、BufferedReader 类似于 BufferedInputStream 不同点在于BufferedReader 读取的是字符charBufferedInputStream读取的是字节byte其内部也带有一个缓冲区1个char[]数组一般用BufferedReader来配合其它字符输入流一起使用来减少IO次数也可以使用mark()函数和 reset()函数重复从缓冲区1个char[]数组中读取字符。③、PushbackReader以将已读取的字符char重新推回输入流中。这样下次调用read()函数时这些字符char就会再次被读取。因此PushbackReader可以从输入流中解析字符char数据。④、StringReader如果输入源是一个字符串的可以使用 StringReader 来将一个字符串转换为字符流。以上4个子类的使用方式请参照1、Java的IO概览一2、Java的IO概览二Reader.class的源码如下package java.io; public abstract class Reader implements Readable, Closeable { //当这个字符输入流进行read操作、skip操作的时候用这个对象锁来同步线程来保证多线程场景下操作这个字符输入流时的线程安全性。 //这个锁对象既可以是Reader.class类型对象本身也可以是其它类型的对象但是最有效的同步方式是在使用synchronized锁定同步代码块的时候一定要用lock变量指向的锁对象而不是将synchronized加到函数上面或者用this关键字同步推荐的用法详情请看skip()函数 //推荐的同步方式如下 /** * public long skip(long n) throws IOException { * ...省略部分代码... * synchronized (lock) { * 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; * } *} * */ //以下2种方式不推荐 //不推荐的同步方式①将synchronized关键字加到函数上面 /** * public synchronized long skip(long n) throws IOException { * ...省略部分代码... * */ //不推荐的同步方式②将lock改成this关键字 /** * public long skip(long n) throws IOException { * ...省略部分代码... * synchronized (this) { * 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; * } *} * */ protected Object lock; //构造函数并将用于多线程场景下线程同步的lock变量指向this当前这个字符输入流对象本身 protected Reader() { this.lock this; } //构造函数并将用于多线程场景下线程同步的lock变量指向其它对象而不是this当前这个字符输入流对象本身 protected Reader(Object lock) { if (lock null) { throw new NullPointerException(); } this.lock lock; } //将从字符输入流中读取n个字符放入到NIO中的CharBuffer对象字符缓冲区对象里面 //当CharBuffer对象字符缓冲区对象中可用的空间0时这个函数是阻塞的直到读取到数据或者抛出异常。 public int read(java.nio.CharBuffer target) throws IOException { int len target.remaining();//计算NIO中的CharBuffer对象字符缓冲区对象中还可以放多少个字符可用的空间 char[] cbuf new char[len];//构建一个长度为len的字符数组char[]len表示CharBuffer对象字符缓冲区对象中还可以放的字符数量 int n read(cbuf, 0, len);//从字符输入流中读取len个字符放到字符数组char[] cbuf的[0,len)索引位置并返回从字符输入流中读取到的字符数量 if (n 0) //如果从字符输入流中读取到的字符数量0则将这些字符假设有n个字符0nlen都放入到CharBuffer对象字符缓冲区对象中 target.put(cbuf, 0, n); return n;//返回n这里的n取值范围为0nlen或者[0,len] } //将从字符输入流中读取1个字符 //和InputStream一样在读取到数据或者抛出异常前这个函数是阻塞的。 public int read() throws IOException { char cb[] new char[1];//构建一个长度为1的字符数组char[] cb //将从字符输入流中读取1个字符放入到字符数组char[] cb的[0,1)索引位置如果字符输入流已经读完了即已经读取到了文件的EOF符号则会返回-1read(cb, 0, 1)函数是留给子类实现的 if (read(cb, 0, 1) -1) return -1;//如果字符输入流已经读完了即已经读取到了文件的EOF符号则返回-1 else return cb[0];//否则返回读取到的字符 } //将从字符输入流中读取length个字符到字符数组char[] cbuf中length表示字符数组char[] cbuf的长度 //和InputStream一样在读取到数据或者抛出异常前这个函数是阻塞的。 public int read(char cbuf[]) throws IOException { return read(cbuf, 0, cbuf.length); } //留给子类实现如果读到了字符输入流的末尾即已经读取到了文件的EOF符号则会返回-1 abstract public int read(char cbuf[], int off, int len) throws IOException; //每一次从字符输入流中最多跳过的字符数量 private static final int maxSkipBufferSize 8192; //执行skip()函数时才会用到用来跳过指定的字符数量时需要借助字符数组char[]来完成 private char skipBuffer[] null; //将从字符输入流中跳过n个字符 public long skip(long n) throws IOException { if (n 0L) //校验如果n0则抛出一个IllegalArgumentException throw new IllegalArgumentException(skip value is negative); //每次跳过的字符最多为8192个 int nn (int) Math.min(n, maxSkipBufferSize); synchronized (lock) {//后续的代码片段只允许单线程执行 if ((skipBuffer null) || (skipBuffer.length nn)) skipBuffer new char[nn];//用于每次跳过指定数量字符每次最多为8192个的字符数组 long r n;//r表示当前线程还没(或者还需要)跳过的字符的总数量 while (r 0) { //当前线程还没(或者还需要)跳过的字符的总数量0时每次执行read()函数从字符输入流中跳过0~8192个字符 //当前线程读到了字符输入流的末尾即已经读取到了文件的EOF符号则会返回-1 int nc read(skipBuffer, 0, (int)Math.min(r, nn)); if (nc -1) break;//中断while循环 r - nc;//将本次从字符输入流中跳过的字符递减就是接下来还没(或者还需要)跳过的字符的总数量 } return n - r;//返回已经从字符输入流中跳过的字符总数量n-r } } //具体逻辑需要子类来重写JDK给这个函数的定义如下 //①、如果返回 true则表示下一次调用read()函数时保证不会阻塞否则返回 false。 //②、即使返回 false 也不能保证下一次调用read()函数时一定会阻塞。 public boolean ready() throws IOException { return false; } //具体逻辑需要子类来重写JDK给这个函数的定义如下 //①、如果返回true表示子类支持mark()函数 //②、如果返回false表示子类不支持mark()函数 public boolean markSupported() { return false; } //标记此字符输入流中的当前位置。随后调用reset()函数时会将此字符输入流重新定位到上次标记的位置从而使得后续的读取操作能够再次读取相同的字符。 //ASCIIReader、BufferedReader、CharArrayReader、FilterReader、Latin1Reader、LineNumberReader、StringReader、UCSReader会重写这个mark()函数其余Reader.class的子类不会重写这个函数。 public void mark(int readAheadLimit) throws IOException { throw new IOException(mark() not supported); } //reset()函数会将此字符输入流重新定位到上次执行mark()函数标记的位置从而使得后续的读取操作能够再次读取相同的字符。 //ASCIIReader、BufferedReader、CharArrayReader、FilterReader、Latin1Reader、LineNumberReader、StringReader、UCSReader会重写这个reset()函数其余Reader.class的子类不会重写这个函数。 public void reset() throws IOException { throw new IOException(reset() not supported); } /** * Closes the stream and releases any system resources associated with * it. Once the stream has been closed, further read(), ready(), * mark(), reset(), or skip() invocations will throw an IOException. * Closing a previously closed stream has no effect. * * exception IOException If an I/O error occurs */ //留给子类实现子类必须遵守以下规则 //①、关闭这个字符输入流并释放与该流相关的系统资源 //②、关闭的字符输入流对象在执行read()函数, ready()函数, mark()函数, reset()函数, 或者 skip()函数 时将抛出一个 IOException. abstract public void close() throws IOException; }1.1、Reader的skip()函数//将从字符输入流中跳过n个字符 public long skip(long n) throws IOException { if (n 0L) //校验如果n0则抛出一个IllegalArgumentException throw new IllegalArgumentException(skip value is negative); //每次跳过的字符最多为8192个 int nn (int) Math.min(n, maxSkipBufferSize); synchronized (lock) {//后续的代码片段只允许单线程执行 if ((skipBuffer null) || (skipBuffer.length nn)) skipBuffer new char[nn];//用于每次跳过指定数量字符每次最多为8192个的字符数组 long r n;//r表示当前线程还没(或者还需要)跳过的字符的总数量 while (r 0) { //当前线程还没(或者还需要)跳过的字符的总数量0时每次执行read()函数从字符输入流中跳过0~8192个字符 //当前线程读到了字符输入流的末尾即已经读取到了文件的EOF符号则会返回-1 int nc read(skipBuffer, 0, (int)Math.min(r, nn)); if (nc -1) break;//中断while循环 r - nc;//将本次从字符输入流中跳过的字符递减就是接下来还没(或者还需要)跳过的字符的总数量 } return n - r;//返回已经从字符输入流中跳过的字符总数量n-r } }如果一个字符输入流中有20000个字符并且此时有多个线程此处假设有2个线程分别为ThreadA和ThreadB要执行此字符输入流的skip()函数ThreadA执行了skip(8000)表示要从这个字符输入流中跳过8000个字符ThreadB执行了skip(10000)表示要从这个字符输入流中跳过10000个字符此时这2个线程同时执行skip()函数的过程如下所示①、ThreadA和ThreadB分别在各自的线程栈中压入skip()函数如下所示②、假如ThreadA在执行下面的代码时先获取到了锁先进入到了Monitor监控synchronized底层是基于操作系统的Monitor来实现的的代码片段synchronized (lock) {//后续的代码片段只允许单线程执行那么ThreadA在进入while循环之前会进行初始化char[]数组的长度为8000和临时变量long r的操作如下所示③、ThreadA只会进入一次while循环ThreadA在进入while循环之后会从字符输入流中读取8000个字符到字符数组char[] skipBuffer中然后就结束while循环并且释放掉获取到的锁对象Object lock并且将从字符输入流中跳过的字符总数量这里是8000返回给该函数的调用者如下所示④、然后ThreadB在执行下面的代码时也获取到了锁进入到了Monitor监控synchronized底层是基于操作系统的Monitor来实现的的代码片段synchronized (lock) {//后续的代码片段只允许单线程执行那么ThreadB在进入while循环之前会重新初始化char[]数组的长度为8192和临时变量long r的操作如下所示⑤、ThreadB第1次进入while循环之后会从字符输入流中读取8192个字符到字符数组char[] skipBuffer中然后更新long r 1808如下所示⑥、ThreadB第2次进入while循环之后会从字符输入流中读取1808个字符到字符数组char[] skipBuffer中然后更新long r 0如下所示⑦、最后ThreadB结束while循环并且释放掉获取到的锁对象Object lock并且将从字符输入流中跳过的字符总数量这里是10000返回给该函数的调用者如下所示二、FilterReader.class源码——字符流的装饰器基类FilterReader 的UML关系图如下所示FilterReader.class的源码如下所示package java.io; /** * Abstract class for reading filtered character streams. * The abstract class codeFilterReader/code itself * provides default methods that pass all requests to * the contained stream. Subclasses of codeFilterReader/code * should override some of these methods and may also provide * additional methods and fields. * * author Mark Reinhold * since JDK1.1 */ public abstract class FilterReader extends Reader { //实际被装饰的字符输入流 protected Reader in; //当用构造函数创建这个装饰器时传入一个被装饰者的字符输入流 protected FilterReader(Reader in) { super(in); this.in in; } //调用实际被装饰的字符输入流的read() 函数 public int read() throws IOException { return in.read(); } //调用实际被装饰的字符输入流的read(char cbuf[], int off, int len) 函数 public int read(char cbuf[], int off, int len) throws IOException { return in.read(cbuf, off, len); } //调用实际被装饰的字符输入流的skip(long n) 函数 public long skip(long n) throws IOException { return in.skip(n); } //调用实际被装饰的字符输入流的ready() 函数 public boolean ready() throws IOException { return in.ready(); } //调用实际被装饰的字符输入流的markSupported() 函数 public boolean markSupported() { return in.markSupported(); } //调用实际被装饰的字符输入流的mark(int readAheadLimit) 函数 public void mark(int readAheadLimit) throws IOException { in.mark(readAheadLimit); } //调用实际被装饰的字符输入流的reset() 函数 public void reset() throws IOException { in.reset(); } //调用实际被装饰的字符输入流的close() 函数 public void close() throws IOException { in.close(); } }三、PushbackReader.class源码——可以回退字符到被读取的字符流中的字符流装饰器类PushbackReader与PushbackInputStream类似只不过PushbackReader提供了有限字符内部定义了一个默认长度为1的char[] buf字符数组的缓冲式回退能力具体过程如下①、当调用unread()函数时会将任意字符可以是从被装饰的输入流中读取的字节也可以是自己定义的字符压入char[] buf字符数组的尾部pos-1的位置②、当后续调用read()函数时优先读取步骤①中这个char[] buf字符数组中被压入的字符。PushbackInputStream.class的相关源码和使用方式请参照13、PushbackInputStream和StreamTokenizer的源码分析和使用方法详细分析3.1、PushbackReader.class的源码分析PushbackReader.class 的UML关系图如下所示PushbackReader.class 的源码如下所示package java.io; public class PushbackReader extends FilterReader { //有限长度的用于回退的字符数组缓冲区默认长度为1 private char[] buf; //可读指针char[] buf有限长度的用于回退的字符数组缓冲区中该指针包括该指针索引之后的所有字符都可以读 private int pos; //构造函数in为被装饰的字符输入流size为char[] buf有限长度的用于回退的字符数组缓冲区的长度 public PushbackReader(Reader in, int size) { super(in); if (size 0) { throw new IllegalArgumentException(size 0); } this.buf new char[size]; this.pos size;//将可读指针指向char[] buf有限长度的用于回退的字符数组缓冲区中最后一个索引size-1之后 } //构造函数in为被装饰的字符输入流 public PushbackReader(Reader in) { this(in, 1);//构造一个默认长度为1的char[] buf用于回退的字符数组缓冲区 } //检查被装饰的字符输入流是否关闭 private void ensureOpen() throws IOException { if (buf null) throw new IOException(Stream closed); } //这个函数在多线程的场景下运行时是线程安全的 //如果char[] buf用于回退的字符数组缓冲区中有可读的字符的话就从该缓冲区中读取1个字符 //如果char[] buf用于回退的字符数组缓冲区中没有可读的字符的话就从被装饰的输入流中读取1个字符 //如果char[] buf用于回退的字符数组缓冲区和被装饰的输入流中都没有可读的字符的话返回-1 public int read() throws IOException { synchronized (lock) {//用Reader.class::lock变量指向的对象锁来同步线程 ensureOpen(); if (pos buf.length) return buf[pos]; else return super.read(); } } //这个函数在多线程的场景下运行时是线程安全的尽可能的从char[] buf用于回退的字符数组缓冲区和被装饰的输入流中读取len个字符到char[] cbuf的[off,offlen)索引位置总共分为以下5种场景 //①、如果char[] buf用于回退的字符数组缓冲区中有len个字符的话就从该缓冲区中读取len个字符到字符数组char[] cbuf的[off,offlen)索引位置 //②、如果char[] buf用于回退的字符数组缓冲区中没有任何字符并且被装饰的字符输入流中有len个字符那就从被装饰的字符输入流中读取len个字符到字符数组char[] cbuf的[off,offlen)索引位置 //③、如果char[] buf用于回退的字符数组缓冲区中没有任何字符并且被装饰的字符输入流中只有availavaillen个字符那就从被装饰的字符输入流中读取avail个字符到字符数组char[] cbuf的[off,offavail)索引位置 //④、如果char[] buf用于回退的字符数组缓冲区中有availavaillen个字符的话就读取avail个字符剩余len-avail个字符从被装饰的字符输入流中读取如果被装饰的字符输入流中没有len-avail个字符的话那就从被装饰的输入流中有多少读取多少直到将被装饰的输入流读取完毕然后将以上2个地方被装饰的输入流用于回退的字符数组缓冲区读取的所有字符假如有x个放入到字符数组char[] cbuf的[off,offx)索引位置 //⑤、如果char[] buf用于回退的字符数组缓冲区和被装饰的字符输入流中都没有任何字符的话返回-1 public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { //检查被装饰的字符输入流是否关闭 ensureOpen(); try { if (len 0) { if (len 0) { throw new IndexOutOfBoundsException(); } else if ((off 0) || (off cbuf.length)) {//相当于off len cbuf.length源码中这样写代码的好处我没看出来 throw new IndexOutOfBoundsException(); } return 0;//要从PushbackReader 对象中读取的len个字符0时返回0 } int avail buf.length - pos;//用于回退的字符数组缓冲区中实际装载了buf.length - pos个字符 if (avail 0) { if (len avail) avail len; System.arraycopy(buf, pos, cbuf, off, avail); pos avail; off avail; len - avail; } if (len 0) { len super.read(cbuf, off, len); if (len -1) { return (avail 0) ? -1 : avail; } return avail len;//场景④中的x就是这里的avail len } return avail; } catch (ArrayIndexOutOfBoundsException e) { throw new IndexOutOfBoundsException(); } } } //一次只可以回推1个字符数据到char[] buf用于回退的字符数组缓冲区中 public void unread(int c) throws IOException { synchronized (lock) { ensureOpen(); if (pos 0)//pos0时表示char[] buf用于回退的字符数组缓冲区中已经没有足够的容量再放置数据所以抛出一个IOException异常。 throw new IOException(Pushback buffer overflow); buf[--pos] (char) c; } } //一次回推char[] cbuf字符数组中[off,offlen)索引位置的len个字符数据到char[] buf用于回退的字符数组缓冲区中 public void unread(char cbuf[], int off, int len) throws IOException { synchronized (lock) { ensureOpen(); if (len pos)//如果char[] buf用于回退的字符数组缓冲区中没有足够的位置放置len个字符则抛出一个IOException throw new IOException(Pushback buffer overflow); pos - len;//如果char[] buf用于回退的字符数组缓冲区中有足够的位置放置len个字符则使用System.arraycopy()函数进行回退 System.arraycopy(cbuf, off, buf, pos, len); } } public void unread(char cbuf[]) throws IOException { unread(cbuf, 0, cbuf.length); } public boolean ready() throws IOException { synchronized (lock) { ensureOpen(); return (pos buf.length) || super.ready(); } } //不支持mark()函数 public void mark(int readAheadLimit) throws IOException { throw new IOException(mark/reset not supported); } //不支持reset()函数 public void reset() throws IOException { throw new IOException(mark/reset not supported); } //不支持mark()函数 public boolean markSupported() { return false; } //关闭被装饰的字符输入流和用于回退的字符数组缓冲区 public void close() throws IOException { super.close(); buf null; } //从char[] buf用于回退的字符数组缓冲区被装饰的字符输入流中跳过n个字符如果char[] buf用于回退的字符数组缓冲区被装饰的字符输入流中的字符数量n则返回实际跳过的字符数量 public long skip(long n) throws IOException { if (n 0L) throw new IllegalArgumentException(skip value is negative); synchronized (lock) { ensureOpen(); int avail buf.length - pos;//从char[] buf用于回退的字符数组缓冲区中跳过的字符 if (avail 0) { if (n avail) { pos n; return n; } else { pos buf.length; n - avail; } } return avail super.skip(n);//从被装饰的字符输入流中跳过的字符累加到从char[] buf用于回退的字符数组缓冲区中跳过的字符 } } }3.2、PushbackReader.class的read()函数和unread()函数package java.io; public class PushbackReader extends FilterReader { //有限长度的用于回退的字符数组缓冲区默认长度为1 private char[] buf; //可读指针char[] buf有限长度的用于回退的字符数组缓冲区中该指针包括该指针索引之后的所有字符都可以读 private int pos; ...省略部分代码... //这个函数在多线程的场景下运行时是线程安全的尽可能的从char[] buf用于回退的字符数组缓冲区和被装饰的输入流中读取len个字符到char[] cbuf的[off,offlen)索引位置总共分为以下5种场景 //①、如果char[] buf用于回退的字符数组缓冲区中有len个字符的话就从该缓冲区中读取len个字符到字符数组char[] cbuf的[off,offlen)索引位置 //②、如果char[] buf用于回退的字符数组缓冲区中没有任何字符并且被装饰的字符输入流中有len个字符那就从被装饰的字符输入流中读取len个字符到字符数组char[] cbuf的[off,offlen)索引位置 //③、如果char[] buf用于回退的字符数组缓冲区中没有任何字符并且被装饰的字符输入流中只有availavaillen个字符那就从被装饰的字符输入流中读取avail个字符到字符数组char[] cbuf的[off,offavail)索引位置 //④、如果char[] buf用于回退的字符数组缓冲区中有availavaillen个字符的话就读取avail个字符剩余len-avail个字符从被装饰的字符输入流中读取如果被装饰的字符输入流中没有len-avail个字符的话那就从被装饰的输入流中有多少读取多少直到将被装饰的输入流读取完毕然后将以上2个地方被装饰的输入流用于回退的字符数组缓冲区读取的所有字符假如有x个放入到字符数组char[] cbuf的[off,offx)索引位置 //⑤、如果char[] buf用于回退的字符数组缓冲区和被装饰的字符输入流中都没有任何字符的话返回-1 public int read(char cbuf[], int off, int len) throws IOException { synchronized (lock) { //检查被装饰的字符输入流是否关闭 ensureOpen(); try { if (len 0) { if (len 0) { throw new IndexOutOfBoundsException(); } else if ((off 0) || (off cbuf.length)) {//相当于off len cbuf.length源码中这样写代码的好处我没看出来 throw new IndexOutOfBoundsException(); } return 0;//要从PushbackReader 对象中读取的len个字符0时返回0 } int avail buf.length - pos;//用于回退的字符数组缓冲区中实际装载了buf.length - pos个字符 if (avail 0) { if (len avail) avail len; System.arraycopy(buf, pos, cbuf, off, avail); pos avail; off avail; len - avail; } if (len 0) { len super.read(cbuf, off, len); if (len -1) { return (avail 0) ? -1 : avail; } return avail len;//场景④中的x就是这里的avail len } return avail; } catch (ArrayIndexOutOfBoundsException e) { throw new IndexOutOfBoundsException(); } } } //一次只可以回推1个字符数据到char[] buf用于回退的字符数组缓冲区中 public void unread(int c) throws IOException { synchronized (lock) { ensureOpen(); if (pos 0)//pos0时表示char[] buf用于回退的字符数组缓冲区中已经没有足够的容量再放置数据所以抛出一个IOException异常。 throw new IOException(Pushback buffer overflow); buf[--pos] (char) c; } } ...省略部分代码... }如果使用者使用的被装饰的字符输入流是CharArrayReader然后执行PushbackReader.class的read()函数和unread()函数时使用如下代码package com.xxx.bio; import java.io.CharArrayReader; import java.io.IOException; import java.io.PushbackReader; import java.io.Reader; public class PushbackReaderTest { public static void main(String[] args) throws IOException { String str 你好世界我将征服你; Reader reader new CharArrayReader(str.toCharArray()); //构建字符回退流 PushbackReader pushbackReader new PushbackReader(reader, 8); int len -1; System.out.println(输出内容:); while ((len pushbackReader.read()) ! -1) { //转为char类型 char c (char) len; if (c ) {//此处为中文 //为号时 先往前读3个再往后倒两个 char[] b1 new char[3]; pushbackReader.read(b1); //往后倒两个 pushbackReader.unread(b1, 0, 2); } else { System.out.print(c); } } } }上面代码的执行结果如下以上代码的整个执行过程分为以下5步①、通过构造函数构建一个长度为8的char[] buf用于回退的字符数组缓冲区和CharArrayReader.class类型的被装饰的字符输入流如下所示PushbackReader pushbackReader new PushbackReader(reader, 8);②、按照顺序从CharArrayReader.class类型的输入流中读取字符直到读取到中文“”时如下所示while ((len pushbackReader.read()) ! -1) { //转为char类型 char c (char) len; if (c ) {//此处为中文 } else { System.out.print(c); } }输出如下你好③、当第一次读取到中文“”之后从被装饰的字符输入流CharArrayReader.class中往char[] b1字符数组中读取3个字符如下所示//为号时 先往前读3个再往后倒两个 char[] b1 new char[3]; pushbackReader.read(b1);④、将步骤③中读入到char[] b1字符数组中的[0,2)索引位置的数据读取到PushbackReader中的char[] buf用于回退的字符数组缓冲区的[6,8)索引位置中如下所示//往后倒两个 pushbackReader.unread(b1, 0, 2);⑤、再次重复执行步骤②中按照顺序从CharArrayReader.class类型的字符输入流中读取字符时先读取PushbackReader中的char[] buf用于回退的字符数组缓冲区的[6,8)索引位置再从CharArrayReader.class类型的字符输入流中读取剩余字符如下所示while ((len pushbackReader.read()) ! -1) { //转为char类型 char c (char) len; if (c ) {//此处为中文 } else { System.out.print(c); } }