web-dev-qa-db-ja.com

Java:ByteBufferとの間の文字列の変換および関連する問題

私はソケット接続にJava NIOを使用しており、プロトコルはテキストベースであるため、文字列をByteBufferに変換してからSocketChannelに書き込み、着信ByteBufferを元に戻す必要があります。現在、私はこのコードを使用しています:

_public static Charset charset = Charset.forName("UTF-8");
public static CharsetEncoder encoder = charset.newEncoder();
public static CharsetDecoder decoder = charset.newDecoder();

public static ByteBuffer str_to_bb(String msg){
  try{
    return encoder.encode(CharBuffer.wrap(msg));
  }catch(Exception e){e.printStackTrace();}
  return null;
}

public static String bb_to_str(ByteBuffer buffer){
  String data = "";
  try{
    int old_position = buffer.position();
    data = decoder.decode(buffer).toString();
    // reset buffer's position to its original so it is not altered:
    buffer.position(old_position);  
  }catch (Exception e){
    e.printStackTrace();
    return "";
  }
  return data;
}
_

これはほとんどの場合に機能しますが、これがこの変換の各方向を実行するのに好ましい(または最も簡単な)方法であるかどうか、または他に試す方法があるかどうかは疑問です。時々、そして一見ランダムに、encode()decode()の呼び出しは、変換のたびに新しいByteBufferオブジェクトを使用していても、_Java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END_例外、または同様の例外をスローします終わらせる。これらのメソッドを同期する必要がありますか? StringsとByteBuffersの間で変換するより良い方法はありますか?ありがとう!

79
DivideByHero

CharsetEncoder および CharsetDecoder APIの説明を確認してください-メソッドの特定のシーケンスに従う必要がありますこの問題を回避するには、calls。たとえば、CharsetEncoderの場合:

  1. 以前に使用したことがない限り、resetメソッドを使用してエンコーダーをリセットします。
  2. 追加の入力が利用できる限り、encodeメソッドを0回以上呼び出し、endOfInput引数にfalseを渡し、呼び出し間で入力バッファーを埋め、出力バッファーをフラッシュします。
  3. 最後にencodeメソッドを呼び出し、endOfInput引数にtrueを渡します。その後
  4. flushメソッドを呼び出して、エンコーダーが内部状態を出力バッファーにフラッシュできるようにします。

ちなみに、これは私がNIOで使用しているアプローチと同じですが、同僚の一部はASCIIのみを使用しているという知識で各文字を直接バイトに変換していますが、おそらくより高速だと思います。

52
Adamski

状況が変わっていない限り、あなたは

public static ByteBuffer str_to_bb(String msg, Charset charset){
    return ByteBuffer.wrap(msg.getBytes(charset));
}

public static String bb_to_str(ByteBuffer buffer, Charset charset){
    byte[] bytes;
    if(buffer.hasArray()) {
        bytes = buffer.array();
    } else {
        bytes = new byte[buffer.remaining()];
        buffer.get(bytes);
    }
    return new String(bytes, charset);
}

通常、buffer.hasArray()は、ユースケースに応じて常にtrueまたは常にfalseになります。実際には、どのような状況でも本当に動​​作させたい場合を除き、不要なブランチを最適化して削除しても安全です。

28
Fuwjax

Adamskiによる回答は良いものであり、一般的なエンコード方法を使用する場合のエンコード操作の手順を説明しています(入力の1つとしてバイトバッファを使用します)

ただし、(この説明で)問題のメソッドは、encode-encode(CharBuffer in)のバリアントです。これは、エンコード操作全体を実装する便利なメソッドです。 (Java P.S.のドキュメントリファレンスを参照してください。)

ドキュメントに従って、したがって、エンコード操作がすでに進行中の場合、このメソッドを呼び出すべきではありませんマルチスレッド環境での静的エンコーダー/デコーダー)。

個人的には、convenienceメソッド(より一般的なエンコード/デコードメソッド)を使用するのが好きです。

ZenBlenderとAdamskiは、コメントでこれを安全に行うための複数の方法オプションをすでに提案しています。ここにすべてをリストします。

  • 各操作に必要なときに新しいエンコーダー/デコーダーオブジェクトを作成します(多数のオブジェクトにつながる可能性があるため効率的ではありません)。または、
  • ThreadLocalを使用して、操作ごとに新しいエンコーダー/デコーダーを作成しないようにします。または、
  • エンコード/デコード操作全体を同期します(プログラムである程度の並行性を犠牲にしない限り、これは好ましくない場合があります)

追伸.

Javaドキュメントリファレンス:

  1. エンコード(便利)メソッド: http://docs.Oracle.com/javase/6/docs/api/Java/nio/charset/CharsetEncoder.html#encode%28Java.nio.CharBuffer%29
  2. 一般的なエンコード方法: http://docs.Oracle.com/javase/6/docs/api/Java/nio/charset/CharsetEncoder.html#encode%28Java.nio.CharBuffer,%20Java.nio.ByteBuffer 、%20boolean%29
14
gurpsin