2016-06-13 30 views
0

私は、JavaのByteBuffer.clear()は実際にはByteBufferのすべてのデータを消去するわけではないので、毎回StringBuilder.append()を実行すると、最終結果は常に残りの文字最後の書き込みからの古いデータであるByteBufferは、どのようにこの問題を修正するのですか?Java ByteBufferデータをクリアする

int byteRead = -1; 
int readCount = 0; 
int BUFFER_SIZE = 256; 
StringBuilder sb = new StringBuilder(); 
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); 
ReadableByteChannel readableByteChannel = Channels.newChannel(is); 
while ((byteRead = readableByteChannel.read(buffer)) > 0 && readCount < 68) { 
    sb.append(new String(buffer.array(), "UTF-8")); 
    buffer.clear(); 
    readCount++; 
} 
+0

Eh?すべてのデータをクリアするのはまさにそのためです。あなたの問題は、文字列を配列全体から直接構築するだけで、読み込みの長さを無視してしまうことです。 – EJP

答えて

4

すでに他の回答が指摘しているとおり、readメソッドで更新されるバッファの位置を考慮する必要があります。あなたの特別な場合には、arrayOffset()は常にゼロになりますが、あなたはより良いあなたがバッファに何かを変更したときに、それは壊れていないこと、方法でコードを書くこと

while ((byteRead = readableByteChannel.read(buffer)) > 0 && readCount < 68) { 
    sb.append(new String(buffer.array(), 
     buffer.arrayOffset(), buffer.arrayOffset()+buffer.position(), "UTF-8")); 
    buffer.clear(); 
    readCount++; 
} 

注:だから、正しいコードは次のようになります割り当てコード。

しかし、このコードは壊れています。複数バイトのUTF-8シーケンスを読み込むと、そのシーケンスの最初のバイトが1回の操作で読み取られ、残りのバイトが次のバイトで読み取られることがあります。これらの不完全なシーケンスからインスタンスStringを作成しようとすると、無効な文字が生成されます。そのほかに、これらのインスタンスをStringBuilderにコピーするだけで、これらのインスタンスを作成することになりますが、これは非常に非効率的です。

だから、それを正しく行うには、次のような何か必要があります。

int readCount = 0; 
int BUFFER_SIZE = 256; 
StringBuilder sb = new StringBuilder(); 
CharsetDecoder dec=StandardCharsets.UTF_8.newDecoder(); 
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE); 
CharBuffer cBuffer= CharBuffer.allocate(BUFFER_SIZE); 
ReadableByteChannel readableByteChannel = Channels.newChannel(is); 
while(readableByteChannel.read(buffer) > 0 && readCount < 68) { 
    buffer.flip(); 
    while(dec.decode(buffer, cBuffer, false).isOverflow()) { 
     cBuffer.flip(); 
     sb.append(cBuffer); 
     cBuffer.clear(); 
    } 
    buffer.compact(); 
    readCount++; 
} 
buffer.flip(); 
for(boolean more=true; more;) { 
    more=dec.decode(buffer, cBuffer, true).isOverflow(); 
    cBuffer.flip(); 
    sb.append(cBuffer); 
    cBuffer.clear(); 
} 

注意、どのように両方、ReadableByteChannelCharsetDecoderプロセスの位置と制限を使用してバッファを。あなたがしなければならないのは、flipcompactを正しくshown in the documentation of compactとすることです。

唯一の例外は、Stringbuilderへの追加です(NIO機能ではありません)。 Stringbuilder.append操作ではバッファからすべての文字を消費することがわかっているため、clear()を使用する必要があります。

このコードでは、任意の数のreadの後に停止するため、特定の(やむを得ない)エラー条件を処理しないことに注意してください。マルチバイトUTF-8シーケンスの途中で切り捨てることは常に可能です。


しかし、これは非常に複雑なロジックは、すでにJREによって実装されていて、あなたがバイトの特定の番号の後に切断のアイデアをあきらめた場合、あなたはそれを利用することができます:今

int readCount = 0; 
int BUFFER_SIZE = 256; 
StringBuilder sb = new StringBuilder(); 
CharBuffer cBuffer= CharBuffer.allocate(BUFFER_SIZE); 
ReadableByteChannel readableByteChannel = Channels.newChannel(is); 
Reader reader=Channels.newReader(readableByteChannel, "UTF-8"); 
while(reader.read(cBuffer) > 0 && readCount < 68) { 
    cBuffer.flip(); 
    sb.append(cBuffer); 
    cBuffer.clear(); 
    readCount++; 
} 

このコードでは、バイトではなく256 × 68文字のうち、に制限されますが、UTF-8エンコードされたデータの場合、これは明らかに以前は気にしなかったマルチバイトシーケンスがある場合にのみ違いがあります。

あなたは明らかに最初の場所でInputStreamを持っているので、最後に、あなたがすべてでReadableByteChannel回り道を必要としない:

int readCount = 0; 
int BUFFER_SIZE = 256; 
StringBuilder sb = new StringBuilder(); 
CharBuffer cBuffer = CharBuffer.allocate(BUFFER_SIZE); 
Reader reader = new InputStreamReader(is, StandardCharsets.UTF_8); 
while(reader.read(cBuffer) > 0 && readCount < 68) { 
    cBuffer.flip(); 
    sb.append(cBuffer); 
    cBuffer.clear(); 
    readCount++; 
} 

これは、「NIOコードされていない」ように見えるかもしれませんが、Reader sはNIOを使っても文字データを読み取る標準的な方法です。代わりはありません。 method Reader.read(CharBuffer)はNIOの最初のリリースではなく、Java 5で手渡されました。

+0

なぜBufferReaderを使用しないのですか? ReadableByteChannelとReaderとBufferReaderのどちらの方が良いでしょうか? –

+0

なぜ 'BufferedReader'を使うのですか? 'BufferedReader'は、そのメソッドを繰り返し使用して、単一のcharを読み込んだり、' BufferedReader'自身のバッファよりも小さなバッファを使用している場合にのみ意味を持ちます。言い換えれば、それは意味をなさない。バッファーが小さすぎる場合は、大きなバッファーを使用してください。また、パフォーマンスに懸念がある場合は、単一のchar readメソッドを使用しないでください。 'BufferedReader'の唯一残っている便利な機能は、行の処理を許可することです。これはここでは無関係です。 – Holger

+0

これはあまり知られていないようですが、十分に大きなバッファを渡すと、 'InputStream'と' Reader'の 'Buffered ...'バリエーションは何もしません。バッファが自分のバッファよりわずかに小さい場合は、不要なコピーによってパフォーマンスが低下することさえあります。その結果、 'Channel' APIはチャンネルのようにバッファされたチャンネルのバリアントを提供しません。常に独自のバッファを用意する必要があります。 – Holger

0

使用position()現在のバッファの位置を取得し、Arrays.copyOfとアレイの一部を取得する:

あなたのケースになります
Arrays.copyOf(buffer.array(), 0, buffer.position()); 

sb.append(new String(Arrays.copyOf(buffer.array(), 0, buffer.position()), "UTF-8")); 

あるいは短い使用している場合適切なStringコンストラクタ:

sb.append(new String(buffer.array(), 0, buffer.position(), "UTF-8")); 

slice(): sb.append(new String(buffer.slice()。array()、 "UTF-8"));

BTW。 "UTF-8"の代わりにStandardCharsets.UTF_8を使用することをお勧めします。

+0

どのパフォーマンスがベストですか? –

+0

@AndyChan最後の1つ –

0

new String(byte[] bytes, int offset, int length, String charsetName())コンストラクタを使用できます。

new String(buffer.array(), 0, byteRead, "UTF-8"); 

これは新しいStringが作成されるときに使用されてから、以前のデータを防ぐことができます。

+0

上記のサンプルは最高ですか?あなたのサンプルのパフォーマンスが最高ですか? –

関連する問題