UTF-16编码 - 为什么要使用复杂的代理对?

3
我一直在研究字符串编码方案,而我分析UTF-16的工作原理时,有一个问题。为什么要使用复杂的代理对来表示21位代码点?为什么不简单地将前面的位存储在第一个代码单元中,将剩余的位存储在第二个代码单元中呢?我有什么遗漏吗?像UTF-8那样直接存储这些位有问题吗?
我考虑的例子: 字符 '' 对应的代码点:128579(十进制) 二进制形式:1 1111 0110 0100 0011(17位) 这是一个17位的代码点。
  • Based on UTF-8 schemes, it will be represented as:

    240 : 11110 000
    159 : 10 011111
    153 : 10 011001
    131 : 10 000011
    
  • In UTF-16, why not do something looks like that rather than using surrogate pairs:

    49159 : 110 0 0000 0000 0111
    30275 : 01 11 0110 0100 0011
    

3
你在第一个单元存储哪些位?你在第二个单元存储哪些位?你如何知道正在处理的是一个21位的数字,而不是两个16位的数字(代码点)?你必须能够明确地确定是否有代理对。这就是UTF-16编码所做的。这也是为什么最大的Unicode代码点是U+10FFFF - 用目前设计的方案只能使用两个代理来编码任何更大的内容。 - Jonathan Leffler
2
此外,您对UTF-8的理解是错误的;它使用类似的方案将21位数字存储在8位字符中。实际上,UTF-8比UTF-16更复杂,因为它针对不同的码点使用四种不同的编码(具有四种不同的长度),而不像UTF-16只使用两种不同的编码。 - Mr Lister
关于你的编辑; 这正是UTF-16的工作方式。唯一的区别是在将位分配给两个字之前,它会从该值中减去65536,每个字10位。所以 110110 000011110111011 11001000011 - Mr Lister
1
还有一个历史维度:当决定2个字节(65536个代码点)不够用时,UTF-16(当时称为UCS2)已经在使用中。此时,代理对解决方案被发明,并保留了两个仍未使用的范围。如果你认为UTF-16很复杂,答案可能是它最初并非如此设计。 - lenz
2
@MrLister... 哇,这是我第一次听到UTF-8比UTF-16更复杂的说法 :) 我想这取决于你对“复杂”一词的定义。 - JoelFan
显示剩余10条评论
1个回答

11

UTF-16的替代方案

我认为你正在提出一种使用16位代码单元的替代格式,类似于UTF-8编码方案 - 让我们将其指定为UTF-EMF-16。

在您的UTF-EMF-16方案中,从U+0000到U+7FFF的码点将被编码为具有MSB(最高有效位)始终为零的单个16位单位。然后,您将保留16位单位,其中2个最高有效位设置为10作为“连续单元”,带有14位有效数据。然后,您将使用三个最高有效位设置为110的16位单位对U+8000到U+10FFFF(当前最大的Unicode码点)进行编码,并具有多达13位有效数据。使用Unicode的当前定义(U+0000 .. U+10FFFF),您永远不需要设置超过13位中的7位。

U+0000 .. U+7FFF   — One 16-bit unit: values 0x0000 .. 0x7FFF
U+8000 .. U+10FFF  — Two 16-bit units:
                     1. First unit  0xC000 .. 0xC043
                     2. Second unit 0x8000 .. 0xBFFF

对于您提供的示例代码点,U+1F683(二进制:1 1111 0110 0100 0011):

First unit:  1100 0000 0000 0111 = 0xC007
Second unit: 1011 0110 0100 0011 = 0xB643

第二个单元与你的示例不同,它将最高有效位从你的示例中的01反转为我的10
为什么UTF-16没有使用这样的方案?
这样的方案可以运作。它是明确的。它可以容纳比Unicode当前允许的更多的字符。UTF-8可以修改为UTF-EMF-8,以处理相同的扩展范围,其中一些字符需要5个字节而不是当前最大的4个字节。UTF-EMF-8带有5个字节可编码高达26位;UTF-EMF-16可以编码27位,但应限制为26位(约6400万个代码点,而不是略多于100万个)。那么,为什么它或类似的东西没有被采用呢?
答案是非常普遍的 - 历史(加上向后兼容性)。
当Unicode首次定义时,人们希望或认为一个16位代码集足够了。使用16位值开发了UCS2编码,并赋予了许多值在0x8000 .. 0xFFFF范围内的含义。例如,U+FEFF是字节顺序标记。
当Unicode方案必须扩展以使Unicode成为更大的代码集时,许多具有最高有效位中的10110位模式的定义字符,因此向后兼容性意味着不能在不破坏与UCS2的兼容性的情况下将上述UTF-EMF-16方案用于UTF-16,这将是一个严重的问题。
因此,标准化机构选择了一种替代方案,其中有高代理项和低代理项。
0xD800 .. 0xDBFF   High surrogates (most signicant bits of 21-bit value)
0xDC00 .. 0xDFFF   Low surrogates (less significant bits of 21-bit value)

低代理项范围提供了10位数据的存储空间——前缀1101 11使用16位中的6位。高代理项范围也提供了10位数据的存储空间——前缀1101 10同样使用16位中的6位。但由于BMP(基本多语言平面——U+0000..U+FFFF)不需要用两个16位单元进行编码,UTF-16编码从高阶数据中减去1,因此可以用来编码U+10000..U+10FFFF。(请注意,尽管Unicode是一个21位编码,但并非所有21位(无符号)数字都是有效的Unicode代码点。值从0x110000..0x1FFFFF是21位数字,但不属于Unicode。)
来自Unicode FAQ - UTF-8、UTF-16、UTF-32和BOM

Q: What’s the algorithm to convert from UTF-16 to character codes?

A: The Unicode Standard used to contain a short algorithm, now there is just a bit distribution table. Here are three short code snippets that translate the information from the bit distribution table into C code that will convert to and from UTF-16.

Using the following type definitions

typedef unsigned int16 UTF16;
typedef unsigned int32 UTF32;

the first snippet calculates the high (or leading) surrogate from a character code C.

const UTF16 HI_SURROGATE_START = 0xD800

UTF16 X = (UTF16) C;
UTF32 U = (C >> 16) & ((1 << 5) - 1);
UTF16 W = (UTF16) U - 1;
UTF16 HiSurrogate = HI_SURROGATE_START | (W << 6) | X >> 10;

where X, U and W correspond to the labels used in Table 3-5 UTF-16 Bit Distribution. The next snippet does the same for the low surrogate.

const UTF16 LO_SURROGATE_START = 0xDC00

UTF16 X = (UTF16) C;
UTF16 LoSurrogate = (UTF16) (LO_SURROGATE_START | X & ((1 << 10) - 1));

Finally, the reverse, where hi and lo are the high and low surrogate, and C the resulting character

UTF32 X = (hi & ((1 << 6) -1)) << 10 | lo & ((1 << 10) -1);
UTF32 W = (hi >> 6) & ((1 << 5) - 1);
UTF32 U = W + 1;

UTF32 C = U << 16 | X;

A caller would need to ensure that C, hi, and lo are in the appropriate ranges. [


2
你的方案可以使用且不会有歧义,但它与UCS2不兼容,而UTF-16是向后兼容的。是的,很多奇怪的决定都是因为“向后兼容性”。这是一个非常普遍的原因,可能会导致被认为是“非明显”的设计决策。 - Jonathan Leffler
1
您已经对数据进行了错误编码。在假设的UTF-EMF-16编码中,十进制55357将被编码为两个单元 - 与从U+8000(十六进制)开始的任何其他Unicode字符一样。同样,十进制56899也是如此(实际上,十六进制中的U+55357和U+56899也是如此)。此外,十进制55357是U+D83D,十进制56899是U+DE43。这是UTF-16中的高代理项和低代理项。这些是UTF-EMF-16编码下保留的、永久未分配的代码点。任何有效的UTF-EMF-16数据都不会编码任何代理项U+D800..U+DFFF(就像有效的UTF-8永远不包含编码的代理项一样)。 - Jonathan Leffler
1
我终于明白为什么Unicode使用代理对了。感谢你的答案。 - Edison Pebojot
1
我曾经苦苦思索如何用2*10位存储21位代理对,但你通过加法技巧完美地解释了这一点,非常感谢! - Jules Sam. Randolph
@JonathanLeffler:这个提议几乎是荒谬的,因为你把主要的UniHan块切成了两半...即使他们重新分配所有代码点,基本的UniHan块已经占据了你提议的8^5 - 184.17%,留下不到5185个代码点用于其他被认为足够重要以避免代理的内容(我还没有减去ASCII部分)。 - RARE Kpop Manifesto
显示剩余9条评论

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