InputStreamReader 的 ready() 方法用于判断底层输入流是否已准备好提供字节数据,且这些字节经过解码后大概率能产生字符,而非判断字符缓冲区中是否有字符。它依赖 InputStream 的 available() 方法,并结合解码器当前状态进行粗略估算,不会解析编码格式、不提前解码、也不检查字符边界。

InputStreamReader 的 ready() 方法并非用于判断“转换缓冲区中是否存在可读字符”,而是用于判断底层输入流是否已准备就绪,能够提供字节数据,并且这些字节在经过解码后有很大概率能够产生至少一个可用字符。
很多开发者初学 Java I/O 时,常常容易混淆一个关键细节:ready() 方法究竟在检查什么?是字符缓冲区中是否已经缓存了字符?还是底层字节流是否有数据可供读取?正确答案是后者,并且它给出的判断仅仅是一个“大致”的估算,并不精确。
ready() 的实际行为
这个方法本质上是在调用底层 InputStream 的 available()(如果支持的话),然后结合当前解码器内部的状态做一个粗略推算。具体来说:
- 它不会解析编码格式(比如 UTF-8 的多字节序列),不会提前解码,也不检查字符边界;
- 如果底层流返回
available() > 0,并且当前解码器没有未完成的多字节序列(比如 UTF-8 中只读到了半个汉字),ready()才可能返回true; - 如果底层流处于阻塞状态(比如网络流没有数据、文件刚读到末尾),或者解码器正在等待更多字节来补全一个字符(比如 UTF-8 中只读到了
0xE4,还需要两个字节才能组成完整字符),那么ready()就会返回false。
因此,ready() 本质上是一个“乐观的预估”——它回答的是“当前调用 read() 大概率不会发生阻塞”,而不是“字符缓冲区中已经存在可读的字符”。
为什么不能靠它判断“缓冲区有字符”
InputStreamReader 内部实际上维护了两层缓冲机制:
- 字节缓冲区:从底层 InputStream 读取原始字节,供解码器进行解码;
- 字符缓冲区(隐式):解码器将字节转换为字符后暂存于此,供后续的
read()调用返回。
关键点在于:ready() 方法根本不暴露字符缓冲区是否非空。它只关心“当前调用 read() 是否大概率不会阻塞”——而不会阻塞的前提是字节足够解码出下一个字符,而不是字符已经解码完成并存储在缓冲区中。因此,认为 ready() 能够判断“缓冲区中有字符”是一种典型的误解。
更可靠的替代方式
如果确实需要确认是否有字符可读,最直接的方法是尝试非阻塞读取:
- 使用
read(char[] cbuf, int off, int len)方法并将len设置为 1,同时配合setReadTimeout()方法(需要将流包装为BufferedInputStream并添加自定义超时逻辑); - 对于文件等长度确定的流,优先使用
BufferedReader,它的ready()更稳定——因为内部维护了字符缓冲,判断逻辑更可靠; - 业务代码中尽量避免轮询
ready(),改用一次read()判断返回值:-1表示流结束,>= 0表示读到了字符。
典型误用场景
下面这段代码看似没有问题,但实际上存在很大隐患:
while (reader.ready()) { // ready() 可能会反复返回 true,但 read() 仍可能阻塞
char c = (char) reader.read(); // 实际仍可能阻塞
}
正确的做法是直接循环读取,直到返回 -1 为止:
int ch;
while ((ch = reader.read()) != -1) {
process((char) ch);
}
这种写法既简洁又可靠,不会因为 ready() 的误判而导致死循环或漏读。一句话总结:ready() 更像是一个“预判提示”,而非一个“确凿证据”。在需要严谨判断字符是否可读的场景下,直接读取并检查返回值永远是最稳妥的方案。
