Java:将 String 与 ByteBuffer 相互转换以及相关问题

Posted

技术标签:

【中文标题】Java:将 String 与 ByteBuffer 相互转换以及相关问题【英文标题】:Java: Converting String to and from ByteBuffer and associated problems 【发布时间】:2010-11-18 03:46:35 【问题描述】:

我使用 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;

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

【问题讨论】:

这将有助于查看异常的完整堆栈跟踪。 【参考方案1】:

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

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

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

【讨论】:

非常感谢,这很有帮助!我发现我确实有多个线程同时调用我的转换函数,即使我没有设计它来允许它。我通过调用 charset.newEncoder().encode() 和 charset.newDecoder().decode() 来修复它,以确保我每次都使用新的编码器/解码器以避免并发问题,或者不必要地在这些对象上进行同步,在我的情况下,它们不共享有意义的数据。我还进行了一些测试,发现每次使用 newEncoder()/newDecoder() 都没有明显的性能差异! 没问题。您可以避免每次都创建新的编码器/解码器,但仍然通过使用 ThreadLocal 保持线程安全,并根据需要为每个线程懒惰地创建一个专用的编码器/解码器(这就是我所做的)。 这行得通吗?新字符串(bb.array(), 0,bb.array().length, "UTF-8")【参考方案2】:

除非事情发生了变化,否则你最好还是选择

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

【讨论】:

【参考方案3】:

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

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

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

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

ZenBlender 和 Adamski 已经提出了多种方法选项来安全地在他们的 cmets 中执行此操作。在这里列出它们:

在每个操作需要时创建一个新的编码器/解码器对象(效率不高,因为它可能导致大量对象)。或者, 使用 ThreadLocal 避免为每个操作创建新的编码器/解码器。或者, 同步整个编码/解码操作(这可能不是首选,除非您的程序可以牺牲一些并发性)

附言

java 文档参考:

    编码(方便)方法:http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer%29 通用编码方式:http://docs.oracle.com/javase/6/docs/api/java/nio/charset/CharsetEncoder.html#encode%28java.nio.CharBuffer,%20java.nio.ByteBuffer,%20boolean%29

【讨论】:

以上是关于Java:将 String 与 ByteBuffer 相互转换以及相关问题的主要内容,如果未能解决你的问题,请参考以下文章

Java 将 string类型与list类型相互转换

Java:将 String 与 ByteBuffer 相互转换以及相关问题

将数组转换为具有一个数组作为成员的结构是否安全?

Java string.valueof的用法以及与parseint的区别

java中如何让byte[]与string类型转换后,保持不变

java 中string与bytes的转换总结