是否可以从输入流中读取单个字符?

时间:2012-04-18 04:52:22

标签: java character-encoding

我从一个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字节长度的字符串。所以对于null终止的字符串,我认为这样的事情可以解决问题:

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 buffering issue类似,但似乎没有回答这个具体问题。

干杯,

1 个答案:

答案 0 :(得分:3)

  

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

是的,评论是正确的,但在其用语中可能有点模糊。

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

  

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

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

所以你可能在字节方面提前阅读,但在代码点方面却没有。这很好,因为用户的键盘(例如)正在生成代码点。


  

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

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

  

实际上,这似乎不是首选实现,因为我总是可以在需要时自行缓冲流。

不,它不是首选实现。是的,你可以(理论上)在编码器下面缓冲流。但是,大多数程序都不是为了构建堆栈而编写的:

Buffered Reader > InputStreamReader > BufferedInputStream > raw InputStream

相反,他们只是这样做:

Buffered Reader > InputStreamReader > raw InputStream

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

  

来自OpenJDK7的标准InputStreamReader似乎可以立即从基本流中读取并缓冲最多8k。

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

  

“每次调用一个InputStreamReader的read()方法都可能导致从底层字节输入流中读取一个或多个字节。为了有效地将字节转换为字符,可以读取更多字节从基础流开始,比满足当前读操作所需要的那样。“

最重要的是,您的用例(您希望在Reader堆栈上绝对没有低级别预读。)是非常不寻常的,并且Java SE标准类库不支持。如果您真的需要这个,请随意实现您自己的InputStreamReader版本,该版本不会提前阅读。但是,如果你 确实需要这个,那就让我感到有些奇怪。

相关问题