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


82

ソケット接続にJavaNIOを使用しており、プロトコルはテキストベースであるため、SocketChannelに書き込む前に文字列をByteBufferに変換し、着信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;
}

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


例外の完全なスタックトレースを確認すると役立ちます。
Michael Borgwardt

回答:


53

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

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

ちなみに、これは私がNIOに使用しているのと同じアプローチですが、同僚の中にはASCIIのみを使用していることを知って、各文字を直接バイトに変換している人もいます。


2
どうもありがとうございました、それはとても役に立ちました!変換関数を同時に呼び出すスレッドが複数あることを発見しましたが、それを許可するように設計していませんでした。charset.newEncoder()。encode()およびcharset.newDecoder()。decode()を呼び出して修正し、同時実行の問題を回避するために毎回新しいエンコーダー/デコーダーを使用していること、またはこれらのオブジェクトで不必要に同期する必要があることを確認しました。私の場合、意味のあるデータを共有していません。また、いくつかのテストを実行しましたが、毎回newEncoder()/ newDecoder()を使用しても、測定可能なパフォーマンスの違いは見つかりませんでした。
DivideByHero 2009

3
問題ない。毎回新しいエンコーダー/デコーダーを作成する必要はありませんが、ThreadLocalを使用し、必要に応じてスレッドごとに専用のエンコーダー/デコーダーを怠惰に作成することで、スレッドセーフを維持できます(これは私が行ったことです)。
アダムスキー

1
これはうまくいくでしょうか?new String(bb.array()、0、bb.array()。length、 "UTF-8")
ベンテック2012

38

物事が変わっていない限り、あなたはより良いです

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になります。実際には、どのような状況でも実際に機能させたい場合を除いて、不要なブランチを最適化しても安全です。


14

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

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

したがって、ドキュメントによると、エンコード操作がすでに進行中の場合はこのメソッドを呼び出さないでください(これは、ZenBlenderのコードで発生していることです-マルチスレッド環境で静的エンコーダー/デコーダーを使用します)。

個人的には、(より一般的なエンコード/デコード方法よりも)便利な方法を使用するのが好きです。それらは、内部のすべてのステップを実行することによって負担を取り除くからです。

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

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

PS

java docsリファレンス:

  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
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.