PushbackInputStream 是通过内部缓冲区模拟字节回退的装饰器流,优先读取缓冲区数据, unread 操作将字节压栈式存入缓冲区,容量有限且不改变底层流位置。

PushbackInputStream 是 Java 中用于“回退一个或多个字节”的装饰器流,它包装一个底层输入流(如 FileInputStream 或 ByteArrayInputStream),并在内部维护一个小型缓冲区(pushback buffer),用来暂存被“退回”的字节。它不真正改变底层流的位置,而是通过缓冲 + 优先读取缓冲区的方式模拟“回退”效果。
核心原理:缓冲优先 + 延迟消费
当你调用 unread(int b) 或 unread(byte[] b) 时,字节不是写回底层流(底层流通常不支持随机回退),而是被存入 PushbackInputStream 自己的缓冲区中。后续调用 read() 会先检查这个缓冲区——如果非空,就从缓冲区取;只有缓冲区为空时,才真正向底层流请求数据。这就实现了逻辑上的“读回去”。
关键使用要点
-
缓冲区大小有限制:构造时需指定 pushback 缓冲区容量(如
new PushbackInputStream(in, 4))。超出容量调用unread()会抛IOException。默认容量是 1,仅支持回退 1 字节。 -
只能 unread 已 read 过的字节:你不能随意 unread 任意字节,必须是刚刚从流中读出来、还没被处理的字节。例如:读到
'{'后想判断是否是 JSON 对象,可先 unread,再交给专门的解析器处理。 -
unread 不影响底层流指针:底层流(如文件)的读位置不会后退,所以不要指望用它替代
RandomAccessFile或mark()/reset()(后者要求底层流支持 mark)。 -
注意字节顺序:多次
unread()是“栈式”压入——后 unread 的字节会先被读出。比如依次 unread(65), unread(66),接下来 read() 会先得 66('B'),再得 65('A')。
简单示例:识别前缀并回退
假设你读取文本流,想检测开头是否为 "TODO",但又不想丢失这 4 个字符供后续处理:
当用户要求为 Java 代码添加注释、修改注释、审查注释、整理注释格式,或要求按照代码注释规范处理 Java 文件时,使用本技能。本技能用于统一生成和维护 Java 类注释模板、按 Git 对比 release 判断版本号、补充每行代码上方中文块注释、调整注释空行规则,并保持 Java 文件 UTF-8 无 BOM 编码。典型触发场景包括:补充类注释、更新 @version、检查 @author 来源、整理行尾注释、把注释放到代码上一行、检查注释是否符合规范、构建前检查 Java 注释风格。
PushbackInputStream pbis = new PushbackInputStream(in, 4);
int b;
StringBuilder prefix = new StringBuilder();
for (int i = 0; i = 0; i--) {
pbis.unread(prefix.charAt(i));
}
}
替代方案对比
若需要更灵活的回退(如任意位置、大范围、多字节无序回退),PushbackInputStream 不适用,应考虑:
-
BufferedInputStream+mark()/reset()(前提是底层流支持 mark,且你提前 mark) - 读入
byte[]或ByteBuffer再用索引控制“虚拟读指针” - 使用
StringReader(对字符串)或ByteArrayInputStream(对字节数组),它们天然可重读
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










