为什么这个Unicode字符在UTF-16编码下会变成6个字节?

8

我在尝试使用一个来自于这个问题的代码片段。我只是简单地添加了一个字节数组,以便使用UTF-16,如下所示:

final char[] chars = Character.toChars(0x1F701);
final String s = new String(chars);
final byte[] asBytes = s.getBytes(StandardCharsets.UTF_8);
final byte[] asBytes16 = s.getBytes(StandardCharsets.UTF_16);

chars有2个元素,这意味着在Java中有两个16位整数(因为代码点在BMP之外)。

asBytes有4个元素,对应32位,这正好可以表示来自chars的两个16位整数,所以很合理。

asBytes16有6个元素,这让我感到困惑。为什么我们最终会多出2个字节,当32位已足以表示这个Unicode字符时?


5
实际的字节是什么?我打赌在UTF-16中有一个字节顺序标记(BOM)。 - Daniel Pryden
2个回答

5
UTF-16 字节以 字节顺序标记 FEFF 开头,以表示该值使用大端编码。根据维基百科,BOM 也用于区分 UTF-16 和 UTF-8:

这两个序列都不是有效的 UTF-8,因此它们的存在表明该文件未使用 UTF-8 编码。

您可以按照 此答案byte[] 转换为十六进制编码的 String
asBytes   = F09F9C81
asBytes16 = FEFFD83DDF01

谢谢,确实是BOM。 - mahonya

3

asBytes有4个元素,对应32位,这是我们从chars中表示两个16位整数所需的位数,因此是有意义的。

实际上,并不是需要用来表示Java中代码点char数量有关。字节数与代码点本身的数字值直接相关。

代码点U+1F701(0x1F701)使用17位(11111011100000001

0x1F701在UTF-8中需要4个字节(F0 9F 9C 81)来编码其17位。请参见Wikipedia上的位分布图表。该算法在RFC 3629中定义。

asBytes16 有6个元素,这让我感到困惑。为什么当32位足以表示这个Unicode字符时,我们会多出2个字节?

根据Java文档中对 StandardCharsets 的说明

UTF_16

public static final Charset UTF_16

Sixteen-bit UCS Transformation Format, byte order identified by an optional byte-order mark

0x1F701需要4个字节的UTF-16编码(D8 3D DF 01)来表示它的17位。请参见Wikipedia上的位分布图表。该算法在RFC 2781中定义。

与UTF-8不同,UTF-16受endian影响,因此StandardCharsets.UTF_16包含一个BOM以指定字节数组中实际使用的字节序。

为避免BOM,请根据需要使用StandardCharsets.UTF_16BEStandardCharsets.UTF_16LE

UTF_16BE

public static final Charset UTF_16BE

Sixteen-bit UCS Transformation Format, big-endian byte order

UTF_16LE

public static final Charset UTF_16LE

Sixteen-bit UCS Transformation Format, little-endian byte order

由于字节顺序已经在名称中暗示了,因此它们不需要在字节数组中包含BOM。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接