组的头部②、当后续调用read()函数时优先读取这个byte[] buf字节数组中被压入的字节当使用PushbackInputStream进行语法解析时需要注意 unread()函数的调用顺序、EOF 处理及嵌套回退风险等。因为语法解析器常需要先预读一个字符判断类型如果发现不是目标类型再退回去而 InputStream.class 本身不支持回退所以PushbackInputStream.class就是为此设计的。很多语法解析需要预读多个字符才能确定 token 类型比如识别 和 、或 /* 注释起始符。PushbackInputStream默认构造函数只分配 1 字节缓冲内部定义了一个默认长度为1的byte[] buf字节数组根本不够用因此需要使用new PushbackInputStream(in, 4)构造一个长度为4的byte[] buf字节数组作为缓冲区用来覆盖大多数双字符操作符和简单分隔符场景如果要支持 Unicode 转义如 \u0061或长标识符前缀判断缓冲区需更大但别盲目设成 1024同时缓冲区byte[] buf字节数组的长度在构造后不可变运行时并无法扩容。比如下面是一个识别数字字面量含小数点时的安全回退示例伪代码如下所示...省略部分代码... int ch in.read(); if (ch .) { int next in.read(); if (Character.isDigit(next)) { // 确认是小数继续解析 parseFractionPart(); } else { // 不是小数退回两个字符. 和 next in.unread(next); in.unread(.); } } else { // 其他情况按原逻辑处理 } ...省略部分代码...1.1、PushbackInputStream的源码分析PushbackInputStream.class 的UML关系图如下所示PushbackInputStream.class的源码如下所示package java.io; public class PushbackInputStream extends FilterInputStream { //有限长度的用于回退的字节数组缓冲区默认长度为1 protected byte[] buf; //可读指针byte[] buf有限长度的用于回退的字节数组缓冲区中该指针包括该指针索引之后的所有字节都可以读 protected int pos; //检查被装饰的输入流是否关闭 private void ensureOpen() throws IOException { if (in null) throw new IOException(Stream closed); } //构造函数in为被装饰的输入流size为byte[] buf有限长度的用于回退的字节数组缓冲区的长度 public PushbackInputStream(InputStream in, int size) { super(in); if (size 0) { throw new IllegalArgumentException(size 0); } this.buf new byte[size]; this.pos size;//将可读指针指向byte[] buf有限长度的用于回退的字节数组缓冲区中最后一个索引size-1之后 } //构造函数in为被装饰的输入流 public PushbackInputStream(InputStream in) { this(in, 1);//构造一个默认长度为1的byte[] buf用于回退的字节数组缓冲区 } //如果byte[] buf用于回退的字节数组缓冲区中有可读的字节的话就从该缓冲区中读取1个字节 //如果byte[] buf用于回退的字节数组缓冲区中没有可读的字节的话就从被装饰的输入流中读取1个字节 //如果byte[] buf用于回退的字节数组缓冲区和被装饰的输入流中都没有可读的字节的话返回-1 public int read() throws IOException { ensureOpen(); if (pos buf.length) { return buf[pos] 0xff; } return super.read(); } //尽可能的从byte[] buf用于回退的字节数组缓冲区和被装饰的输入流中读取len个字节到byte[] b的[off,offlen)索引位置总共分为以下5种场景 //①、如果byte[] buf用于回退的字节数组缓冲区中有len个字节的话就从该缓冲区中读取len个字节到字节数组byte[] b的[off,offlen)索引位置 //②、如果byte[] buf用于回退的字节数组缓冲区中没有任何字节并且被装饰的输入流中有len个字节那就从被装饰的输入流中读取len个字节到字节数组byte[] b的[off,offlen)索引位置 //③、如果byte[] buf用于回退的字节数组缓冲区中没有任何字节并且被装饰的输入流中只有availavaillen个字节那就从被装饰的输入流中读取avail个字节到字节数组byte[] b的[off,offavail)索引位置 //④、如果byte[] buf用于回退的字节数组缓冲区中有availavaillen个字节的话就读取avail个字节剩余len-avail个字节从被装饰的输入流中读取如果被装饰的输入流中没有len-avail个字节的话那就从被装饰的输入流中有多少读取多少直到将被装饰的输入流读取完毕然后将以上2个地方被装饰的输入流用于回退的字节数组缓冲区读取的所有字节假如有x个放入到字节数组byte[] b的[off,offx)索引位置 //⑤、如果byte[] buf用于回退的字节数组缓冲区和被装饰的输入流中都没有任何字节的话返回-1 public int read(byte[] b, int off, int len) throws IOException { //检查被装饰的输入流是否关闭 ensureOpen(); if (b null) { throw new NullPointerException(); } else if (off 0 || len 0 || len b.length - off) {//相当于off len b.length源码中这样写代码的好处我没看出来 throw new IndexOutOfBoundsException(); } else if (len 0) { return 0;//要从PushbackInputStream 对象中读取的len个字节0时返回0 } int avail buf.length - pos;//用于回退的字节数组缓冲区中实际装载了buf.length - pos个字节 if (avail 0) { if (len avail) { avail len; } System.arraycopy(buf, pos, b, off, avail); pos avail; off avail; len - avail; } if (len 0) { len super.read(b, off, len); if (len -1) { return avail 0 ? -1 : avail; } return avail len;//场景④中的x就是这里的avail len } return avail; } //一次只可以回推1个字节数据到byte[] buf用于回退的字节数组缓冲区中 public void unread(int b) throws IOException { ensureOpen(); if (pos 0) {//pos0时表示byte[] buf用于回退的字节数组缓冲区中已经没有足够的容量再放置数据所以抛出一个IOException异常。 throw new IOException(Push back buffer is full); } buf[--pos] (byte)b; } //一次回推byte[] b字节数组中[off,offlen)索引位置的len个字节数据到byte[] buf用于回退的字节数组缓冲区中 public void unread(byte[] b, int off, int len) throws IOException { ensureOpen(); if (len pos) {//如果byte[] buf用于回退的字节数组缓冲区中没有足够的位置放置len个字节则抛出一个IOException throw new IOException(Push back buffer is full); } pos - len;//如果byte[] buf用于回退的字节数组缓冲区中有足够的位置放置len个字节则使用System.arraycopy()函数进行回退 System.arraycopy(b, off, buf, pos, len); } public void unread(byte[] b) throws IOException { unread(b, 0, b.length); } //返回byte[] buf用于回退的字节数组缓冲区被装饰的输入流中可以被使用的字节总数量 public int available() throws IOException { ensureOpen(); int n buf.length - pos;//先计算用于byte[] buf用于回退的字节数组缓冲区中可以被使用的字节总数量 int avail super.available();//再计算被装饰的输入流中可以被使用的字节总数量 return n (Integer.MAX_VALUE - avail) ? Integer.MAX_VALUE : n avail;//byte[] buf用于回退的字节数组缓冲区中可以被使用的字节总数量被装饰的输入流中可以被使用的字节总数量 } //从byte[] buf用于回退的字节数组缓冲区被装饰的输入流中跳过n个字节如果byte[] buf用于回退的字节数组缓冲区被装饰的输入流中的字节数量n则返回实际跳过的字节数量 public long skip(long n) throws IOException { ensureOpen(); if (n 0) { return 0; } long pskip buf.length - pos;//从byte[] buf用于回退的字节数组缓冲区中跳过的字节 if (pskip 0) { if (n pskip) { pskip n; } pos pskip; n - pskip; } if (n 0) { pskip super.skip(n);//从被装饰的输入流中跳过的字节累加到从byte[] buf用于回退的字节数组缓冲区中跳过的字节 } return pskip; } public boolean markSupported() { return false; }