views:

3801

answers:

1

I am using Java NIO for my socket connections, and my protocol is text based, so I need to be able to convert Strings to ByteBuffers before writing them to the SocketChannel, and convert the incoming ByteBuffers back to Strings. Currently, I am using this code:

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;
}

This works most of the time, but I question if this is the preferred (or simplest) way to do each direction of this conversion, or if there is another way to try. Occasionally, and seemingly at random, calls to encode() and decode() will throw a "java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END" exception, or similar, even if I am using a new ByteBuffer object each time a conversion is done. Do I need to synchronize these methods? Any better way to convert between Strings and ByteBuffers? Thanks!

+1  A: 

Check out the CharsetEncoder and CharsetDecoder API descriptions - You should follow a specific sequence of method calls to avoid this problem. For example, for CharsetEncoder:

  1. Reset the encoder via the reset method, unless it has not been used before;
  2. Invoke the encode method zero or more times, as long as additional input may be available, passing false for the endOfInput argument and filling the input buffer and flushing the output buffer between invocations;
  3. Invoke the encode method one final time, passing true for the endOfInput argument; and then
  4. Invoke the flush method so that the encoder can flush any internal state to the output buffer.

By the way, this is the same approach I am using for NIO although some of my colleagues are converting each char directly to a byte in the knowledge they are only using ASCII, which I can imagine is probably faster.

Adamski
Thanks very much, that was very helpful! I discovered that I did have multiple threads calling my conversion functions concurrently, even though I had not designed it to allow that. I fixed it by calling charset.newEncoder().encode() and charset.newDecoder().decode() to ensure I was using a new encoder/decoder each time to avoid concurrency issues, or needlessly having to synchronize on those objects, which don't share meaningful data in my case. I also ran some tests and found no measurable performance difference in using newEncoder()/newDecoder() each time!
ZenBlender
No problem. You could avoid having to create new encoders / decoders each time but still remain thread safe by using ThreadLocal, and lazily creating a dedicated encoder / decoder per thread as needed (this is what I've done).
Adamski