是UTF8内射映射

Is UTF8 injective mapping?

本文关键字:映射 UTF8      更新时间:2023-10-16

我们编写了一个C++应用程序,需要知道:

UTF8文本编码是从字节到字符的内射映射,这意味着每个字符(字母…)仅以一种方式编码?因此,例如字母'Ž'不能同时编码为3231和32119。

这在很大程度上取决于你对"字母"的看法。

UTF8是Unicode的一小部分。

基本上至少有三个层次:字节、代码点和字形簇。一个码点可以被编码成一个或多个字节,根据一定的编码,如UTF8, UTF16或UTF32。这种编码是唯一的(因为所有替代方法都被声明为无效)。然而,代码点并不总是字形,因为有所谓的组合字符。这种组合字符跟在基本字符后面,顾名思义,与基本字符组合。例如,有一个组合字符U+0308 combined DIAERESIS,它在前面的字母上面放了一个DIAERESIS(¨)。因此,如果它跟在例如a (U+0061 LATIN SMALL LETTER a)后面,则结果是ä。然而,字母ä也有一个单一的代码点(U+00E4拉丁小写字母a WITH DIAERESIS),所以这意味着代码序列U+0061 U+0308和U+00E4描述了同一个字母。

因此,每个码点都有一个有效的UTF8编码(例如U+0061是"141",U+0308是"314210",U+00e4是"303244"),但是字母ä由码点序列U+0061 U+0308编码,即在UTF8中字节序列"141314210"和单个码点U+00e4编码,即字节序列"303244"。

更糟糕的是,由于Unicode制定者决定组合字母基础字母之后,而不是在它之前,所以在看到下一个代码点之前,您无法知道您的字形是否完整(如果它不是组合代码点,那么您的字母就完成了)。

Valid UTF-8确实对每个字符进行了唯一编码。然而,也存在所谓的过长序列,它们符合一般的编码方案,但根据定义是无效的,因为只有最短的序列才能用于编码字符。

例如,有一个UTF-8的衍生物称为修改的 UTF-8,它将NUL编码为过长序列0xC0 0x80而不是0x00,以获得与以空结尾的字符串兼容的编码。

如果你问的是字素簇(即用户感知的字符)而不是字符,那么即使是有效的UTF-8也是模棱两可的。但是,Unicode定义了几种不同的规范化形式,如果您将自己限制为规范化字符串,那么UTF-8确实是内射的。

有点离题:这里是一些我想出的ASCII艺术,以帮助可视化字符的不同概念。垂直分开的是抽象机器级别。请随便想出更好的名字…

                         [user-perceived characters]<-+
                                      ^               |
                                      |               |
                                      v               |
            [characters] <-> [grapheme clusters]      |
                 ^                    ^               |
                 |                    |               |
                 v                    v               |
[bytes] <-> [codepoints]           [glyphs]<----------+

回到主题:此图还显示了当使用字节比较抽象字符串时可能出现的问题。特别是(假设是UTF-8),程序员需要确保

  • 字节序列是有效的,即不包含过长的序列或编码非字符码点
  • 字符序列是标准化的,所以等价的字素簇有一个唯一的表示

首先你需要一些术语:

  • 字母:(抽象概念,非Unicode)您想要表示的某个字母或符号。
  • 代码点:与Unicode字符相关联的数字。
  • Grapheme cluster:对应于单个字母的Unicode码点序列,例如:a + ́代表字母á
  • 字形:(字体级别的概念,而不是Unicode):字母的图形表示。

每个码点(例如:U+1F4A9)在UTF-8中获得唯一的字节表示(例如:0xF0 0x9F 0x92 0xA9)。

一些字母可以用几种不同的方式表示为码点(即:作为不同的字素簇)。例如:á可以表示为单个码点á(拉丁小写字母a WITH ACUTE),也可以表示为a(拉丁小写字母a) + ́(组合急性重音)的码点。Unicode有几种规范规范化形式来处理这个问题(例如:NFC或规范规范化形式C是一种具有较少代码点的松散规范化形式,而NFD是完全分解的)。

然后,还有一些字母的结合力(例如:)和其他一些与表示相关的字母变体(例如:上标,无间断空格,单词不同位置的不同形状的字母,…)。其中一些是Unicode的,允许从旧字符集到旧字符集的无损往返转换。Unicode有兼容性规范化形式(NFKC和NFKD)来处理这个问题。

是。UTF-8只是编码Unicode字符的标准方法。这样一来,每个Unicode字符只有一种编码方式。

有点离题了:知道一些字符在外观上非常相似(与人类)可能是有用的,但它们仍然不同-例如,在西里尔字母中有一个符号看起来非常类似于'/'。

是的,有点像。如果使用得当,每个unicode码点在UTF-8中应该只以一种方式编码,但这在一定程度上是因为任何字符都应该只使用最短的适用UTF-8字节序列。

然而,如果不满足此要求,用于编码字符的方法可以以多种方式编码许多字符——尽管不合适,但在某些情况下可以这样做。

例如,'Z'可以编码为0x5a{0xa1, 0x9a}(以及其他),尽管唯一的0x5a被认为是正确的,因为它是最短的序列。