如何获取用于构造String的byte[]?

How to get the byte[] used to construct a String?

我有一些二进制数据被编码为 UTF-8 字符串。如何从字符串中取回原始数据?二进制数据没有特定的字符编码,所以我不确定什么样的转换会给我我想要的。考虑以下最小示例:

byte[] input = { -84 };
String s = new String(input, Charset.forName("UTF8"));
System.out.println(Arrays.toString(s.getBytes())); // prints [63] 
System.out.println(Arrays.toString(s.getBytes("UTF8"))); // prints [-17, -65, -67]

我正在寻找一种方法来回报我 [-84]

一般来说,你不能。并非所有字节序列都是有效的 UTF-8。因此,数据可能在(容错)byte[]->char[]->byte[] 进程中损坏。

虽然您可以使用 ISO_8859_1 编码,但它是 byte<->char

的一对一映射

这不是一个罕见的问题。许多古老的协议,如 HTTP,以 ISO_8859_1 字符或 C 的 char 类型开始。较新版本的规范会说它基于 "octets",又名 "bytes"。如果您的 API 使用字符串来表示它们,ISO_8859_1 通常是更好的选择。

字节 -84 (0xAC) 本身不是有效的 UTF-8 字节序列。 (UTF-8 仅在多字节字符序列中使用 0 到 127 范围之外的字节,用于编码外来字符。)因此 UTF-8 解码器将输入字节替换为字符 U+FFFD,Unicode "replacement character". (这可能会在您的控制台中显示为一个普通的问号。)无法从该字符串中恢复原始字节数组,因为其他无效字节序列也会解码为替换字符。

您可以执行以下操作:

  • 将二进制数据解释为在字节和字符之间具有一对一映射的字符编码。 ISO-8859-1 是最方便的选择,因为它是保证在任何 Java 实现上可用的六种基本编码之一,并且具有预定义的 StandardCharsets constant. Any old DOS codepage (e.g., Charset.forName("CP437")) 如果存在也可以使用。

  • 将您自己的 byte[] 转换为 char[]。确切的映射是任意的,可以是任何你喜欢的,只要它是无损的。潜在地,您可以通过在每个 Java char 中打包两个字节来将内存中每个字符串的大小减半,因为该类型是 16 位宽,但这可能不值得大惊小怪。

  • Encode the binary data as text, such as by Base64。这种编码本身会使数据变长,但如果字符串得到额外的编码,则可能会使数据变短。

    例如,如果您尝试将二进制数据作为参数传递 in a URL,则对其进行 Base64 编码是有意义的。取一个长度为 256 的字节数组,其中包含每个可能的字节值的 1(它将用作任何均匀随机、压缩或加密数据的模型)。如果在 Base64 中编码并去除填充并使用修改后的 URL-safe Base64 字母表,它将占用 342 个字符,但在 URL-编码并作为 URL查询参数。然而,相同的字节数组 "decoded" 就好像它是一个 ISO-8859-1 字符串一样只需要 256 个字符,但是当它被放入 URL 中时,它会增长到一个庞大的 634 个字符,因为 URL 编码针对纯文本而非二进制数据进行了优化。

  • 首先避免将二进制数据作为字符串传递。尽可能直接使用字节数组。如果您的目的是获得字符串特性,例如不可变性和 indexOf 对字节数据的搜索,那么为数组制作一个包装器 class 会更好。