登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  java教程

Java PushbackReader 怎样回退多读的一个字符

来源:17golang原创

时间:2026-10-29 04:59:02 206浏览 收藏

Java PushbackReader 可以用 unread(c) 把多读的一个字符放回自己的回退缓冲,让下一次从同一个 PushbackReader 读取时重新得到它。读取数字前缀时,遇到第一个非数字就回退;遇到 -1 则直接结束,不能把 EOF 当作字符放回。

官方 API:https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/PushbackReader.html

后续读取必须使用同一个包装对象

考虑输入 731;next:数字读取方法需要确认数字已经结束,因此会读到分号,但分号可能属于调用方处理的语法。我会让数字读取方法只返回数字前缀,把分隔符留给上层;这样调用方知道自己还需要处理什么。

关键是所有后续读取继续经过同一个 PushbackReader。原始 Reader、PushbackReader 和它的回退缓冲不是三条可以随意交替访问的通道。直接读取被包装的原始 Reader,会绕过回退缓冲。示例把底层 StringReader 只交给包装对象,不再单独保存并使用。

数字读取方法、PushbackReader、底层Reader与回退缓冲的依赖关系
图1:字符流包装结构图。调用方与解析方法共享包装对象,底层 Reader 与回退缓冲归属不同位置;这是静态说明图。

数字方法只回退第一个非数字

下面是完整 Java 示例,可保存为 PrefixReaderDemo.java。它只识别 ASCII 数字 0 到 9,不解析正负号、小数、空白或 Unicode 数字;最大长度由调用方提供。本文没有执行该代码,后文结果属于按逻辑推导的预期。

import java.io.IOException;
import java.io.PushbackReader;
import java.io.StringReader;

public class PrefixReaderDemo {
    static String readDigits(PushbackReader reader, int maxDigits)
            throws IOException {
        if (maxDigits  '9') {
                reader.unread(c);
                return digits.toString();
            }
            if (digits.length() == maxDigits) {
                // 超长作为不可继续解析的错误,不尝试把整个已读字段倒回。
                throw new IOException("数字字段超过允许长度");
            }
            digits.append((char) c);
        }
        return digits.toString();
    }

    public static void main(String[] args) {
        // 调用方拥有流;解析方法借用同一个对象,不负责关闭。
        try (PushbackReader reader =
                new PushbackReader(new StringReader("731;next"), 1)) {
            String digits = readDigits(reader, 8);
            int delimiter = reader.read();
            System.out.println("数字=" + digits);
            // 区分EOF,避免把-1强转成普通字符。
            System.out.println("分隔符=" +
                    (delimiter == -1 ? "EOF" : Character.toString((char) delimiter)));
        } catch (IOException e) {
            // 失败即终止本次解析,不假装流位置仍停在字段起点。
            System.err.println("读取失败:" + e.getMessage());
            System.exit(1);
        }
    }
}

对于示例输入,数字前缀预期为 731,调用方接下来读到分号。上层随后可以继续读取 next。这里始终最多只有一个待回退字符,因此容量 1 足够;这不是在为整段数字保存回滚副本。

EOF 与空前缀需要分别约定

我会在接口说明中保留以下输入情形,避免自动调用时把“没读到数字”误判为成功消费了一个字段:

输入返回数字前缀调用方下一次读取的预期
731;next731分号
;next空字符串仍是分号
731731EOF
空输入空字符串EOF

当输入以非数字开始时,本方法把这个字符放回,流在逻辑上没有向前推进。调用方必须消费分隔符、选择另一条解析规则或终止;如果不改变状态就反复调用 readDigits,会一直得到空字符串。这是调用契约问题,不是扩大缓冲可以解决的问题。

read() 返回的 -1 是流结束标记。unread(int) 会把参数按 char 放入缓冲,所以把 -1 放回不会恢复“流结束”,反而会引入一个字符。先保留 int 再检查 -1,是这个小方法里我最看重的细节。

容量、异常和关闭责任一起写清楚

PushbackReader 不支持 mark/reset。回退缓冲满时 unread 会抛 IOException;默认构造器只有一个字符空间,显式容量构造器要求正数。示例不吞掉 unread 异常,因为一旦没能保留分隔符,继续解析就会建立在错误位置上。

EOF判断、容量约定、异常终止与调用方资源关闭的关系
图2:接口责任结构图。解析方法负责输入判断与异常传播,调用方负责继续读取和资源关闭;这是静态说明图,不代表执行证据。

示例中调用方用 try-with-resources 关闭 PushbackReader,底层 Reader 也随包装关闭。readDigits 只是借用,不提前 close,否则调用方无法继续消费剩余内容。实际接入共享流时,应先确认谁拥有关闭权。

超长数字字段的异常同样需要明确:本示例已经消费了触发超长的那个数字,并不保证失败后可恢复到字段开头。我的取舍是把这种异常作为本次解析失败直接退出,避免制造“失败等于零消费”的错误承诺。

接入调用链时关注三个问题

缓冲容量是不是最多能读取的字符数?不是,它限制的是还没被重新读出的回退字符数量。可以持续读取很长的底层流,只是本文另外限制数字字段长度。

能否把任意位置恢复成以前的内容?不能。unread 保存的是你提供的字符,不是通用历史游标或事务回滚机制。

一读一退由不同线程调用可以吗?本示例要求单一解析调用链拥有读取权。不要把单个方法的同步理解为“read、判断、unread”组合操作自动不可分割;多方抢读应由更外层统一协调。

我会把这种做法用于只需少量预读的字符解析。只要保证同一包装对象、EOF 不回退、失败后不盲目继续,以及调用方能推进输入,单字符回退就很容易交接。

参考:PushbackReader API;Reader API。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>