2

我从 InputStreamReader 开始,但这缓冲了它的输入,从输入流中读取的内容超过了所需的内容(如其 Java 文档中所述)。深入研究源代码(java 版本“1.7.0_147-icedtea”),我找到了 sun.nio.cs.StreamDecoder 类,其中包含以下注释:

// In order to handle surrogates properly we must never try to produce
// fewer than two characters at a time.  If we're only asked to return one
// character then the other is saved here to be returned later.

所以我想问题变成了“这是真的吗,如果是,为什么?” 根据我(非常基本!)对 JLS 所需的 6 个字符集的理解,始终可以确定读取单个字符所需的确切字节数,因此无需预读。

背景是我有一个二进制文件,其中包含一堆具有不同编码(数字、字符串、单字节标记等)的数据。基本格式是一组重复的字节标记(指示数据类型),如果该类型需要,则后跟可选数据。包含字符数据的两种类型是以空字符结尾的字符串和前一个 2 字节长度的字符串。因此,对于以空结尾的字符串,我认为这样的事情可以解决问题:

String readStringWithNull(InputStream in) throws IOException {
  StringWriter sw = new StringWriter();
  InputStreamReader isr = new InputStreamReader(in, "UTF-16LE");
  for (int i; (i = isr.read()) > 0; ) {
    sw.write(i);
  }
  return sw.toString();
}

但是 InputStreamReader 从缓冲区中提前读取,因此对基本 InputStream 的后续读取操作丢失了数据。对于我的特殊情况,我知道所有字符都是 UTF-16LE BMP(有点像 UCS-2LE),所以我只是围绕它进行编码,但我仍然对上述一般情况感兴趣。

另外,我看到了类似的InputStreamReader 缓冲问题,但似乎没有回答这个特定问题。

干杯,

4

1 回答 1

4

所以我想问题变成了“这是真的吗,如果是,为什么?”

是的,评论是正确的,尽管其措辞可能有点晦涩难懂。

单个 Unicode 代码点的 UTF-8 编码由 1 到 4 个字节组成;请参阅 Wikipedia UTF-8 示例。. 但在某些情况下,Unicode 代码点不能表示为一个 Java char。因此,解码器可能必须将多字节 UTF-8 序列解码为两个 Javachar值……并保留其中一个值。

根据我(非常基本!)对 JLS 所需的 6 个字符集的理解,始终可以确定读取单个字符所需的确切字节数,因此无需预读。

对于可变长度编码,它比这更复杂一些。解码器提前读取足够的字节以形成一个 Unicode 代码点。对于 UTF-8,这将在 1 到 4 个字节之间,并且通过检查字节它知道何时停止。然后它将字节解码为 1 或 2 个 UTF-16 代码单元(即 Javachar值),传递第一个,并保存第二个。

因此,您可能会在字节方面提前阅读,而不是在代码点方面。这很好,因为用户的键盘(例如)正在生成代码点。


此外,应该可以创建一个无缓冲读取器,它的性能与标准读取器完全相同,但一次只能从底层流中提取一个代码点,因此可以在我上面的示例中使用。

是的,应该可以做到这一点。然而,这样的阅读器需要进行多达 4 次单独的系统调用才能读取单个代码点,这是非常低效的。

事实上,这似乎不是一个首选的实现,因为如果需要,我总是可以自己缓冲流。

不,它不是首选实现。是的,您可以(理论上)在编码器下方自己缓冲流。然而,大多数程序并不是为了像这样构建堆栈而编写的:

Buffered Reader > InputStreamReader > BufferedInputStream > raw InputStream

相反,他们只是这样做:

Buffered Reader > InputStreamReader > raw InputStream

这将使您的方法执行得非常缓慢。(并且您尝试向普通的 Joe 程序员解释为什么他应该在堆栈中放置一个额外的显式缓冲层。)

OpenJDK7 的标准 InputStreamReader 似乎可以立即从基本流中读取和缓冲高达 8k 的数据。

如果他们不做这样的事情,表现会很糟糕......见上文。此外,这是记录在案的行为- javadoc 说:

“每次调用 InputStreamReader 的 read() 方法之一都可能导致从底层字节输入流中读取一个或多个字节。为了实现字节到字符的有效转换,可以从底层流中提前读取比是满足当前读取操作所必需的。”

底线是您的用例(您绝对不希望在Reader堆栈上进行低级预读。)非常不寻常,并且不受 Java SE 标准类库的支持。如果您真的需要这个,请随意实现您自己的版本,InputStreamReader而不是提前阅读。但我觉得你真的需要这个有点奇怪

于 2012-04-18T05:24:57.897 回答