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 つの文字セットに関する私の (非常に基本的な!) 理解から、1 文字を読み取るのに必要な正確なバイト数をいつでも特定できるため、先読みは必要ありません。

背景は、さまざまなエンコーディング (数値、文字列、1 バイト トークンなど) のデータの束を含むバイナリ ファイルがあったことです。基本的な形式は、バイト マーカー (データのタイプを示す) の繰り返しセットで、その後に必要に応じてそのタイプにオプションのデータが続きます。文字データを含む 2 つの型は、NULL で終了する文字列と、前に 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 バイトで構成されます。ウィキペディアのUTF-8 の例を参照してください。. ただし、場合によっては、Unicode コードポイントを 1 つの Java として表すことができませんchar。したがって、デコーダーはマルチバイトの UTF-8 シーケンスを 2 つの Java値としてデコードし、そのうちの 1 つを保持する必要がある可能性があります。char

JLS に必要な 6 つの文字セットに関する私の (非常に基本的な!) 理解から、1 文字を読み取るのに必要な正確なバイト数をいつでも特定できるため、先読みは必要ありません。

可変長エンコーディングの場合は、これよりも少し複雑です。デコーダーは、1 つの Unicode コードポイントを形成するのに十分なバイトだけ先読みします。これは、UTF-8 の場合は 1 から 4 バイトの間であり、バイトを調べることでいつ停止するかがわかります。次に、バイトを 1 つまたは 2 つの UTF-16 コード単位 (Java 値) としてデコードしchar、最初のバイトを配信し、2 番目のバイトを保存します。

したがって、コードポイントではなく、バイト単位で先読みしている可能性があります。ユーザーのキーボード (たとえば) がコードポイントを生成しているため、これは問題ありません。


また、標準のリーダーとまったく同じように機能するが、基本となるストリームから一度に 1 つのコードポイントのみをプルする、バッファリングされていないリーダーを作成できるはずなので、上記の例で使用できます。

はい、これを行うことができるはずです。ただし、そのようなリーダーは、単一のコードポイントを読み取るために最大 4 つの個別のシステム コールを作成する必要があり、非常に非効率的です。

実際、必要に応じていつでも自分でストリームをバッファリングできるため、これは好ましい実装ではないように思われます。

いいえ、推奨される実装ではありません。はい、(理論的には) ストリームを自分でエンコーダーの下にバッファーすることができます。ただし、ほとんどのプログラムは、次のようにスタックを構築するようには作成されていません。

Buffered Reader > InputStreamReader > BufferedInputStream > raw InputStream

代わりに、これを行うだけです:

Buffered Reader > InputStreamReader > raw InputStream

これにより、アプローチの実行が非常に遅くなります。(そして、平均的な Joe プログラマーに、明示的なバッファリング レイヤーを追加で追加する必要がある理由を説明してみてください。)

OpenJDK7 の標準の InputStreamReader は、ベース ストリームから最大 8k をすぐに読み取り、バッファリングするように見えます。

彼らがこのようなことをしなければ、パフォーマンスはひどいものになるでしょう...上記を参照してください。さらに、これは文書化された動作です-javadocは次のように述べています:

「InputStreamReader の read() メソッドのいずれかを呼び出すたびに、基礎となるバイト入力ストリームから 1 つまたは複数のバイトが読み取られる可能性があります。バイトから文字への効率的な変換を可能にするために、基礎となるストリームからより多くのバイトを先読みすることができます。現在の読み取り操作を満たすために必要です。」

肝心なのは、あなたのユースケース (スタック上で低レベルの先読みをまったくしたくない場合Reader) は非常に珍しいものであり、Java SE 標準クラス ライブラリではサポートされていないということです。これが本当に必要な場合は、InputStreamReader先読みしない独自のバージョンを自由に実装してください。しかし、あなたが本当にこれを必要とするのは少し奇妙に思えます。

于 2012-04-18T05:24:57.897 に答える