pushbackinputstream通过内部缓冲区暂存字节实现“回退”,并非真正撤回底层流数据;默认缓冲区仅1字节,需构造时指定容量(如new pushbackinputstream(in, 4))以支持多字节回退,且只能回退已读取的字节,不可插入新内容。

PushbackInputStream 怎么让字节流“吐回去”
它不是真正把数据从底层流里撤回,而是把字节暂存在自己内部缓冲区里,下次 read() 时优先返回这些“推回”的字节。关键在缓冲区大小——默认只有 1 字节,想回退多个字符必须显式指定容量。
常见错误现象:IOException: Push back buffer is full,就是你 unread() 太多次、超了构造时设的缓冲区大小。
- 构造时传入
new PushbackInputStream(in, 4)才能安全回退最多 4 个字节 - 回退的必须是之前
read()过的字节,不能随便unread(65)插入新内容 - 如果底层流已关闭,
unread()会直接抛IOException,不是静默失败
用 PushbackInputStream 解析带前缀的二进制协议
比如读一个文件,开头 2 字节是版本号,后面是变长结构体;你得先 peek 一下再决定怎么解析。这时候不能靠“多读一次再重连流”,因为很多流(如 SocketInputStream)不支持重复读。
实操建议:
立即学习“Java免费学习笔记(深入)”;
- 用
unread(byte[])把刚读出的版本号字节数组塞回去,后续交给专用解析器统一处理 - 别用
unread(int)逐字节回退——它会把高 24 位全截掉,unread(0xFF00)实际只回退0x00 - 注意字节序:回退的是原始字节,不是 Java 的
char或字符串,别和InputStreamReader混用
为什么不用 BufferedInputStream + mark/reset
mark() 和 reset() 看似功能类似,但限制极多:markSupported() 返回 false 的流(如 FileInputStream 在某些 JDK 版本下)根本不能用;而且 mark() 位置一旦被后续读取超过 readlimit,reset() 就失效。
而 PushbackInputStream 不依赖底层流能力,只要构造成功,unread() 就可靠。代价是内存——缓冲区是额外分配的,且无法动态扩容。
- 若协议只需单字节判断(如跳过 BOM、识别 magic number),
PushbackInputStream更轻量可控 - 若需回退几百字节或随机 seek,该换
ByteArrayInputStream包装整个数据块,而不是硬撑PushbackInputStream
和 Reader 层的字符回退冲突吗
绝对冲突。不要把 PushbackInputStream 套在 InputStreamReader 外面,也别用它处理 UTF-8 等可变长编码的字符边界。比如你读到半个 UTF-8 字符(如 0xC3),把它 unread() 回去,下层 InputStreamReader 再读时可能拼出非法序列,触发 MalformedInputException。
正确做法:
- 纯字节协议(HTTP header、自定义二进制包)→ 用
PushbackInputStream - 需要按字符/行处理文本 → 改用
PushbackReader,它操作的是 Unicode 码元,缓冲单位是char - 混用场景(如先 sniff 编码再转 Reader)→ 先用
PushbackInputStream读前几个字节判断 BOM,确定编码后新建InputStreamReader,别复用原流
缓冲区大小是唯一需要提前想清楚的参数,设小了容易爆,设大了白占堆内存;其余逻辑都挺直白——读、判断、不想消费就 unread(),仅此而已。










