当 Java 中的字节转换为字符串时,幕后会发生什么?

What happens under the hood when bytes converted to String in Java?

我在 Java 中尝试将字节转换为字符串时遇到问题,代码如下:

byte[] bytes = {1, 2, -3};

byte[] transferred = new String(bytes, Charsets.UTF_8).getBytes(Charsets.UTF_8);

并且原始字节与传输的字节不一样,分别是

[1, 2, -3]
[1, 2, -17, -65, -67]

我曾经以为是UTF-8字符集映射为负数“-3”。所以我把它改成“-32”。但是传输的数组保持不变!

[1, 2, -32]
[1, 2, -17, -65, -67] 

所以我非常想知道当我调用 new String(bytes) 时到底发生了什么:)

构造函数的文档中有一行:

This method always replaces malformed-input and unmappable-character sequences with this charset's default replacement string.

这绝对是罪魁祸首,因为 -3 在 UTF-8 中是无效的。顺便说一句,如果你真的有兴趣,你可以随时下载 rt.jar 的源代码,并调试它。

并非所有字节序列在 UTF-8 中都有效。

UTF-8 是一种智能方案,每个代码点的字节数可变,每个字节的形式表示同一代码点后面有多少其他字节。

参考this table:

现在让我们看看它如何适用于您的 {1, 2, -3}:

字节1(十六进制0x01,二进制00000001)和2(十六进制0x02,二进制00000010)独立,没问题。

字节-3(十六进制0xFD,二进制11111101)是一个6字节序列的起始字节(在current UTF-8 standard中实际上是非法的),但是你的字节数组没有这样的序列。

您的 UTF-8 无效。 Java UTF-8 解码器将此无效字节 -3 替换为 Unicode 代码点 U+FFFD REPLACEMENT CHARACTER (also see this)。在 UTF-8 中,代码点 U+FFFD 是十六进制 0xEF 0xBF 0xBD(二进制 11101111 10111111 10111101),在 Java 中表示为 -17, -65, -67.

在Java中,byte是有符号的,其中负值大于127。而您使用的那些(-3 = 0xFD,-32 = 0xE0)在UTF-8中无效,所以它们都被转换为 Unicode 代码点 U+FFFD REPLACEMENT CHARACTER,它被转换回 UTF-8 为 0xEF = -17, 0xBF = -65, 0xBD = -67.

您不能期望随机字节值被正确解释为 UTF-8 文本。

您得到的编码值 [-17, -65, -67] 对应于 Unicode 代码点 0xFFFD。如果您查找该代码点,the Unicode specification 会告诉您 0XFFFD "used to replace an incoming character whose value is unknown or unrepresentable in Unicode." 正如其他人指出的那样,没有任何后续代码单元的 -3 是损坏的 UTF-8,所以这个角色很合适