Java:将字符串与ByteBuffer相互转换以及相关问题


81

我正在使用Java NIO进行套接字连接,并且我的协议是基于文本的,因此我需要能够将字符串转换为ByteBuffer,然后再将其写入SocketChannel,并将传入的ByteBuffer转换回String。目前,我正在使用以下代码:

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()将抛出一个 java.lang.IllegalStateException: Current state = FLUSHED, new state = CODING_END异常,或类似的,即使我使用的是新的ByteBuffer对象每次转换完成时间。我需要同步这些方法吗?在字符串和ByteBuffer之间进行转换的更好方法?谢谢!


这将有助于查看该异常的完整堆栈跟踪。
Michael Borgwardt

Answers:


53

请查看CharsetEncoderCharsetDecoderAPI描述-您应该按照特定的方法调用顺序进行操作,以避免出现此问题。例如,对于CharsetEncoder

  1. 通过reset方法重置编码器,除非以前未使用过;
  2. encode只要可能有其他输入可用,就调用该方法零次或多次,并false为endOfInput参数传递并填充输入缓冲区并在两次调用之间刷新输出缓冲区;
  3. encode最后一次调用该方法,并传递true给endOfInput参数。然后
  4. 调用该flush方法,以便编码器可以将任何内部状态刷新到输出缓冲区。

顺便说一句,这是我在NIO中使用的相同方法,尽管我的一些同事在知道仅使用ASCII的情况下将每个char直接转换为一个字节,我想这可能更快。


2
非常感谢,这非常有帮助!我发现我确实有多个线程可以同时调用转换函数,即使我没有设计允许它执行转换的函数。我通过调用charset.newEncoder()。encode()和charset.newDecoder()。decode()来解决此问题,以确保每次都使用新的编码器/解码器来避免并发问题,或者不必要在这些对象上进行同步,在我看来,这些共享的数据并不有意义。我还进行了一些测试,发现每次使用newEncoder()/ newDecoder()都没有可测量的性能差异!
DivideByHero

3
没问题。您可以避免每次都必须创建新的编码器/解码器,但仍可以通过使用ThreadLocal保持线程安全,并根据需要懒惰地为每个线程创建专用的编码器/解码器(这是我所做的)。
Adamski

1
这行得通吗?新的String(bb.array(),0,bb.array()。length,“ UTF-8”)
bentech 2012年

36

除非情况发生变化,否则您最好选择

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的回答是一个很好的答案,它描述了使用常规编码方法(将字节缓冲区作为输入之一)时的编码操作步骤。

但是,所讨论的方法(在此讨论中)是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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.