使用 Files.newBufferedReader() 读取文件和直接构造读取器的不同结果

Different results reading file with Files.newBufferedReader() and constructing readers directly

似乎 Files.newBufferedReader() 对 UTF-8 的要求比天真的选择更严格。

如果我创建一个只有一个字节 128 的文件——所以,不是有效的 UTF-8 字符——如果我在 [=15= 上构造一个 BufferedReader,它会被愉快地读取] Files.newInputStream() 的结果,但 Files.newBufferedReader() 抛出异常。

这个代码

try (
    InputStream in = Files.newInputStream(path);
    Reader isReader = new InputStreamReader(in, "UTF-8");
    Reader reader = new BufferedReader(isReader);
) {
    System.out.println((char) reader.read());
}

try (
    Reader reader = Files.newBufferedReader(path);
) {
    System.out.println((char) reader.read());
}

有这样的结果:

�
Exception in thread "main" java.nio.charset.MalformedInputException: Input length = 1
    at java.nio.charset.CoderResult.throwException(CoderResult.java:281)
    at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:339)
    at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
    at java.io.InputStreamReader.read(InputStreamReader.java:184)
    at java.io.BufferedReader.fill(BufferedReader.java:161)
    at java.io.BufferedReader.read(BufferedReader.java:182)
    at TestUtf8.main(TestUtf8.java:28)

这有记录吗?是否有可能通过 Files.newBufferedReader() 获得宽大的行为?

区别在于在这两种情况下如何构造用于解码 UTF-8 的 CharsetDecoder

对于new InputStreamReader(in, "UTF-8"),解码器是使用以下方法构造的:

Charset cs = Charset.forName("UTF-8");

CharsetDecoder decoder = cs.newDecoder()
          .onMalformedInput(CodingErrorAction.REPLACE)
          .onUnmappableCharacter(CodingErrorAction.REPLACE);

这明确指定无效序列仅被替换为标准替换字符。

Files.newBufferedReader(path) 使用:

Charset cs = StandardCharsets.UTF_8;

CharsetDecoder decoder = cs.newDecoder();

在这种情况下,onMalformedInputonUnmappableCharacter 未被调用,因此您将获得默认操作,即抛出您看到的异常。

似乎没有办法改变 Files.newBufferedReader 的作用。在查看代码时,我没有看到任何对此进行记录的内容。

据我所知,它没有在任何地方记录,并且不可能让 newBufferedReader 表现得很好。

不过应该记录下来。事实上,在我看来,缺乏文档是一个有效的 Java 错误,即使修改后的文档最终说“无效的字符集序列导致未定义的行为”。

此外,由于没有关于该主题的文档,我认为您不能放心地依赖您所观察到的行为。 InputStreamReader 的未来版本完全有可能默认使用严格的内部 CharsetDecoder。

因此,为了保证宽大的行为,我会让你的代码更进一步:

try (
    InputStream in = Files.newInputStream(path);
    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder()
        .onMalformedInput(CodingErrorAction.REPLACE);
    Reader isReader = new InputStreamReader(in, decoder);
    Reader reader = new BufferedReader(isReader);
) {
    System.out.println((char) reader.read());
}