87

我正在使用 Java NIO 进行套接字连接,并且我的协议是基于文本的,因此我需要能够在将字符串写入 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我需要同步这些方法吗?有更好的方法在字符串和字节缓冲区之间进行转换吗?谢谢!

4

3 回答 3

54

查看API 描述CharsetEncoder-CharsetDecoder您应该遵循特定的方法调用序列来避免这个问题。例如,对于CharsetEncoder

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

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

于 2009-08-09T23:35:07.890 回答
44

除非事情发生了变化,否则你最好

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() 将始终为真或始终为假,具体取决于您的用例。在实践中,除非你真的希望它在任何情况下都能正常工作,否则优化掉你不需要的分支是安全的。

于 2015-06-15T22:45:03.837 回答
14

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

但是,有问题的方法(在此讨论中)是 encode - encode(CharBuffer in)的变体。这是一种实现整个编码操作的便捷方法。(请参阅 PS 中的 java 文档参考)

根据文档,如果编码操作已经在进行中,则不应调用此方法(这就是 ZenBlender 的代码中发生的事情——在多线程环境中使用静态编码器/解码器)。

就个人而言,我喜欢使用方便的方法(而不是更通用的编码/解码方法),因为它们通过执行幕后的所有步骤来减轻负担。

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
于 2012-09-26T02:58:20.170 回答