在 Java 中转换 ByteBuffer 和 String 的问题

Posted

技术标签:

【中文标题】在 Java 中转换 ByteBuffer 和 String 的问题【英文标题】:Problems Converting Between ByteBuffer and String in Java 【发布时间】:2016-10-02 19:51:51 【问题描述】:

我目前正在开发一个应用程序,用户可以在其中通过十六进制编辑器界面编辑 ByteBuffer,还可以通过 JTextPane 编辑相应的文本。我当前的问题是因为 JTextPane 需要一个字符串,我需要在显示值之前将 ByteBuffer 转换为字符串。但是,在转换过程中,无效字符会被 charsets 默认替换字符替换。这会压缩无效值,因此当我将其转换回字节缓冲区时,无效字符值将替换为默认替换字符的字节值。有没有一种简单的方法可以保留字符串中无效字符的字节值?我已经阅读了以下 *** 帖子,但通常人们只想替换不可打印的字符,我需要保留它们。

Java ByteBuffer to String

Java: Converting String to and from ByteBuffer and associated problems

是否有一种简单的方法可以做到这一点,或者我是否需要跟踪文本编辑器中发生的所有更改并将它们应用到 ByteBuffer?

这是演示问题的代码。代码使用 byte[] 而不是 ByteBuffer 但问题是一样的。

        byte[] temp = new byte[16];
        // 0x99 isn't a valid UTF-8 Character
        Arrays.fill(temp,(byte)0x99);

        System.out.println(Arrays.toString(temp));
        // Prints [-103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103]
        // -103 == 0x99

        System.out.println(new String(temp));
        // Prints ����������������
        // � is the default char replacement string

        // This takes the byte[], converts it to a string, converts it back to a byte[]
        System.out.println(Arrays.toString(new String(temp).getBytes()));
        // I need this to print [-103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103, -103]
        // However, it prints
        //[-17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67, -17, -65, -67]
        // The printed byte is the byte representation of �

【问题讨论】:

我认为这需要代码。听起来像一个错误。也可能是一个概念错误:您在转换为字节时遇到了哪些确切的文本序列? 我已更新问题以包含显示问题的代码。这不是我的代码中的错误,它应该默认以这种方式工作。 【参考方案1】:

尤其是 UTF-8 会出错

    byte[] bytes = 'a', (byte) 0xfd, 'b', (byte) 0xe5, 'c';
    String s = new String(bytes, StandardCharsets.UTF_8);
    System.out.println("s: " + s);

需要一个 CharsetDecoder。可以忽略(=删除)或替换有问题的字节,或者默认情况下:抛出异常。

对于 JTextPane,我们使用 html,因此我们可以在 <span> 中写入违规字节的十六进制代码,并给它一个红色背景。

    ByteBuffer byteBuffer = ByteBuffer.wrap(bytes);
    CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
    CharBuffer charBuffer = CharBuffer.allocate(bytes.length * 50);
    charBuffer.append("<html>");
    for (;;) 
        try 
            CoderResult result = decoder.decode(byteBuffer, charBuffer, false);
            if (!result.isError()) 
                break;
            
         catch (RuntimeException ex) 
        
        int b = 0xFF & byteBuffer.get();
        charBuffer.append(String.format(
            "<span style='background-color:red; font-weight:bold'> %02X </span>",
            b));
        decoder.reset();
    
    charBuffer.rewind();
    String t = charBuffer.toString();
    System.out.println("t: " + t);

代码并没有反映一个很好的 API,但可以玩一下。

【讨论】:

这是一个非常好的主意,我什至没有考虑过。我看到的唯一问题是,当我将 JTextPane 的文本从 String 转换回字节 [] 时,会有大量额外的标记驻留在 JTextPane 的文本中。您对如何解决这个问题有任何想法吗? A replaceAll("&lt;[^&gt;]*&gt;", "") 或更好的带有模式匹配器的循环。 A JTextPane 还允许使用样式文本 (StyledDocument) 并使用与文本分开的属性,但这很麻烦,特别是如果您想允许编辑。但是您可以使用byteBuffer.position() 来标记这些字节。 我认为这种方法可能最能满足我对这个特定项目的需求。我希望我可以做一些更简单的事情,但这可能不得不做。谢谢!【参考方案2】:

您认为new String(temp).getBytes() 会为您做什么?

我可以告诉你,它做了一些坏事。

    它使用默认编码将temp 转换为String,这可能是错误的,并且可能会丢失信息。 它使用默认编码将结果转换回字节数组。

要将byte[] 转换为String,您必须始终将Charset 传递给String 构造函数,否则直接使用解码器。由于您使用的是缓冲区,因此您可能会发现解码器 API 很合适。

要将String 转换为byte[],您必须始终调用getBytes(Charset),以便知道您使用的是正确的字符集。

基于 cmets,我现在怀疑您的问题是您需要编写类似以下的代码才能将 UI 的字节转换为十六进制。 (然后是相应的返回。)

String getHexString(byte[] bytes) 
    StringBuilder builder = new StringBuilder();
    for (byte b : bytes) 
       int nibble = b >> 4;
       builder.append('0' + nibble);
       nibble = b & 0xff;
       builder.append('0' + nibble);
    
    return builder.toString();

【讨论】:

我了解最佳实践规定 getBytes 和 String 构造函数都应采用 Charset。如果我将 Charset 传递给 String 构造函数,问题仍然存在。 new String (temp, "UTF-8") 会引发 UnsupportedEncodingException 异常,因为 byte[] 包含无法映射的字符。我觉得答案将需要使用 CharsetDecoder API,但我还没有看到任何使用它进行类似操作的示例。 如果它包含非UTF-8,如果你想保留所有信息,你可以不将它转换为字符串。您需要将每个byte 转换为两个十六进制数字;您使用的 API 无法做到这一点。 @JustinA.Moore 所以,既然我们已经找到了概念性错误/错误,您究竟想对不可映射的字符做什么。根据定义,它们是,不可映射,因此您必须为他们制定一些超出Charset 视野的计划。 它们可以作为任何东西打印在 JTextArea 内(一个空格,上面的 � 字符,无论如何。它们没有与之关联的字符),我只需要底层字节将 String 转换回 byte[] 或 ByteBuffer 时保持不变。 除非您编写自定义字符集,否则您无法拥有它。没有提供所有可能字节值的往返的字符集。 something 将被映射到替换字符,因此总是会丢失。

以上是关于在 Java 中转换 ByteBuffer 和 String 的问题的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Java 中将 ByteBuffer 转换为 FileInputStream?

Java:转换 ByteBuffer 多维数组

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

在 Java 中将 ByteBuffer 转换为字符串

Java bytebuffer 将三个字节转换为 int

将异常的堆栈跟踪转换为 byte[] 数组或 ByteBuffer (Java) 是不是更有效?