PushbackInputStream是Java中用于字节流的回溯装饰器,支持预读和退字节,需指定缓冲区大小;字符流应使用PushbackReader。

Java 中 PushbackInputStream 只能用于字节流,不能直接用于字符流。它本质是对 InputStream 的装饰,提供“把刚读过的字节退回去”的能力,常用于需要预读(peek)或回溯解析的场景(比如词法分析、自定义协议解析)。字符流需用 PushbackReader 实现类似功能。
字节流中使用 PushbackInputStream 回退单个或多个字节
PushbackInputStream 构造时可指定缓冲区大小(默认为 1),决定最多能回退几个字节。关键操作是 read() 和 unread(int b)(或 unread(byte[] b))。
- 调用
read()读取一个字节后,可用unread(b)将其推回缓冲区,下次read()会先返回这个回退的字节 - 缓冲区满时再调用
unread()会抛出IOException,所以要确保回退字节数不超过构造时设定的容量 - 示例:识别以 "AB" 开头的数据块,但只希望把 "A" 当作前导,B 留给后续逻辑处理
InputStream in = new ByteArrayInputStream("ABCDEF".getBytes());
PushbackInputStream pbis = new PushbackInputStream(in, 2); // 支持回退最多 2 字节
int a = pbis.read(); // 读 'A' → 65
int b = pbis.read(); // 读 'B' → 66
if (a == 'A' && b == 'B') {
pbis.unread(b); // 把 'B' 推回
pbis.unread(a); // 把 'A' 推回 —— 注意顺序:后读的先推回,才能保持原始顺序
}
// 下次 read() 会再次得到 'A'
字符流中实现类似回退:必须用 PushbackReader
PushbackInputStream 是字节层面的,无法感知字符编码和多字节字符(如 UTF-8 中的中文)。若在字符流中需要回退,应使用 java.io.PushbackReader,它包装 Reader,支持按字符回退。
- 构造时同样可指定缓冲区大小(单位是字符,不是字节)
- 调用
read()后用unread(int c)或unread(char[] cbuf)回退 - 对含中文、emoji 等 Unicode 字符更安全,避免字节错位导致乱码
StringReader sr = new StringReader("你好World");
PushbackReader pbr = new PushbackReader(sr, 4);
int c1 = pbr.read(); // '你' → \u4f60
int c2 = pbr.read(); // '好' → \u597d
pbr.unread(c2);
pbr.unread(c1); // 下次 read() 仍得 '你'
常见误区与注意事项
-
不能混用字节流和字符流的回退类:用
PushbackInputStream包装FileReader会失败,因为FileReader是Reader子类,不是InputStream - 缓冲区大小需合理预估:回退需求超过容量就会失败;设太大浪费内存,太小限制灵活性
-
异常处理不可少:
unread()在缓冲满或流已关闭时抛IOException,应捕获并处理 -
不适用于所有场景:若需频繁、深度回溯(如完整语法树遍历),建议用
BufferedReader+ 自行维护位置索引,或改用更高级的解析库(如 ANTLR)
何时该选 PushbackInputStream 而非其他方式
适合轻量级、局部回溯,比如跳过 BOM、识别变长头部、处理简单分隔符(如遇到 '#' 就跳过整行注释,但需先确认是否真为注释)。
立即学习“Java免费学习笔记(深入)”;
- 优势:JDK 原生、零依赖、开销小、语义清晰
- 替代方案对比:
BufferedInputStream.mark()/reset()要求底层支持 mark(如FileInputStream不支持),且需提前mark(),不如unread()灵活及时 - 真实场景举例:解析自定义二进制配置文件,前 2 字节是版本号,但只有版本 ≥ 2 才启用扩展字段,此时可先读 2 字节判断,再决定是否 unread 并跳过扩展区


















