Java:将字符串转换为ByteBuffer和来自ByteBuffer的字符串以及相关问题

Div*_*ero 79 java string nio bytebuffer character-encoding

我使用Java NIO进行套接字连接,我的协议是基于文本的,所以我需要能够在将字符串转换为SocketChannel之前将其转换为ByteBuffers,并将传入的ByteBuffers转换回字符串.目前,我正在使用此代码:

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;
}
Run Code Online (Sandbox Code Playgroud)

这在大多数情况下都有效,但我怀疑这是否是执行此转换的每个方向的首选(或最简单)方法,或者是否有其他方法可以尝试.偶尔,和看似随意,调用encode()decode()将抛出一个 java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END异常,或类似的,即使我使用每次转换完成一次新的ByteBuffer对象.我需要同步这些方法吗?在字符串和ByteBuffers之间转换的更好方法是什么?谢谢!

Ada*_*ski 52

查看CharsetEncoderCharsetDecoderAPI描述 - 您应该遵循特定的方法调用序列以避免此问题.例如,对于CharsetEncoder:

  1. 通过该reset方法重置编码器,除非之前没有使用过;
  2. 调用encode方法零次或多次,只要有额外的输入可用,传递falseendOfInput参数并填充输入缓冲区并在调用之间刷新输出缓冲区;
  3. encode最后一次调用方法,传递trueendOfInput参数; 然后
  4. 调用该flush方法,以便编码器可以将任何内部状态刷新到输出缓冲区.

顺便说一句,这与我用于NIO的方法相同,尽管我的一些同事正在将每个字符串直接转换为一个字节,知道他们只使用ASCII,我可以想象它可能更快.

  • 非常感谢,这非常有帮助!我发现我确实有多个线程同时调用我的转换函数,即使我没有设计它允许它.我通过调用charset.newEncoder().encode()和charset.newDecoder().decode()来修复它,以确保每次都使用新的编码器/解码器以避免并发问题,或者不必要地同步这些对象,在我的情况下,它不共享有意义的数据.我还运行了一些测试,发现每次使用newEncoder()/ newDecoder()时没有可衡量的性能差异! (2认同)
  • 没问题.您可以避免每次都创建新的编码器/解码器,但仍然使用ThreadLocal保持线程安全,并根据需要懒洋洋地为每个线程创建一个专用的编码器/解码器(这就是我所做的). (2认同)

Fuw*_*jax 33

除非事情发生变化,否则你会更好

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);
}
Run Code Online (Sandbox Code Playgroud)

通常,buffer.hasArray()将始终为true或始终为false,具体取决于您的用例.在实践中,除非您真的希望它在任何情况下都能工作,否则可以安全地优化您不需要的分支.


小智 14

Adamski的回答很好,并描述了使用通用编码方法(将字节缓冲区作为输入之一)时的编码操作中的步骤

但是,所讨论的方法(在本讨论中)是编码 - 编码(CharBuffer in)的变体.这是一种实现整个编码操作便捷方法.(请参阅PS中的java docs参考)

根据文档,如果编码操作已在进行中,则不应调用此方法(这是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