PushbackInputStream 通过缓冲区实现字节“回退”,而非真正倒带;它将已读字节暂存于头部缓冲区,后续 read() 优先从中读取,用于协议解析中识别错误后安全回退,缓冲区大小需预估准确且固定,unread 操作遵循后进先出栈序,close 前须确保缓冲区清空。

PushbackInputStream 不是让流“倒带”,而是把已读字节暂存在自己维护的缓冲区头部,后续 read() 优先从这里取。它解决的核心问题是:已从底层流读了若干字节,发现格式不符(比如不是 JSON 而是二进制魔数),又无法重开流时,如何安全地“吐回去”供重新解析。
回退必须基于已读字节,且缓冲区大小要预估准确
它只能回退 刚刚通过 read() 读过、但尚未消费的字节,不能插入任意新数据,也不能越过流当前读取位置往前推。缓冲区是固定大小的数组,构造时必须显式指定,例如 new PushbackInputStream(in, 8)。若解析协议需预读魔数(4 字节)+ 版本(2 字节)+ 标志位(1 字节),那就至少预留 7 字节——给缓冲区留 1 字节余量更稳妥。缓冲区满时调用 unread() 会直接抛 IOException,不是静默失败。
回退顺序决定读取顺序,像栈一样后进先出
unread(0x01) 后 unread(0x02),缓冲区内容是 [0x02, 0x01];下次 read() 先返回 0x02。这个特性在识别多字符操作符(如 ==、!=、/*)时很关键:你得按相反顺序 unread。例如读到 '/' 后又读到 '*',确认是注释起始,那后续逻辑应先 unread('*') 再 unread('/'),才能保证下一轮 read() 拿到 '/' 开头的完整序列。
单字节读取 + 逐字节 unread 是最可控的组合
read(byte[]) 批量读取后调用 unread(byte[]),容易遗漏“已读但未判定”的字节。比如 read(b, 0, 5) 读了 5 字节,再 unread(new byte[]{0xA, 0xB}),后续 read(b, 0, 8) 只返回 A、B,不会自动补上前 5 字节。因此推荐统一走单字节路径:
- 用 read() 逐字判断协议状态(如跳过空白、识别起始符)
- 一旦需要回退,用 unread(int) 精确退回刚读的那个字节
- 若需退多个,按读取逆序多次调用 unread(int)
close 前必须清空缓冲区,否则数据永久丢失
close() 会直接关闭底层流,不检查也不消费 pushback 缓冲区里的字节。如果 unread(0xFF) 后没再 read 就 close,这个 0xFF 就消失了,且无任何提示。建议在关闭前加校验逻辑:
- 解析分支结束时,确保所有 unread 的字节已被重新读取
- 或在 close() 前断言:if (pushbackStream.available() > 0) throw new IllegalStateException("unconsumed bytes")
- 避免把 unread 当作“暂存”,而应视作“临时占位”,每个解析路径都要明确处理完或丢弃


















