【问题标题】:Is UTF8 injective mapping?UTF8 是单射映射吗?
【发布时间】:2011-12-28 05:12:13
【问题描述】:

我们编写了一个C++ 应用程序并且需要知道这一点:

UTF8 文本编码是从字节到字符的单射映射,这意味着每个字符(字母...)都只以一种方式编码吗?所以,例如字母“Ž”不能同时编码为 3231 和 32119。

【问题讨论】:

  • 每个 UTF 字符都是一个编码的 Unicode“代码点”。每个代码点都有一个关联的字形(如“Ž”)。但是 Unicode 也允许字形的组合。这么简单的答案没有。但是每个“代码点”都应该是唯一的。
  • 只是为了让我们的术语直截了当:在 unicode 中,根据定义,任何字符都对应于单个代码点(但也有非字符代码点);然后,还有用户感知的字符,也称为字素簇,这就是我们在谈论非 unicode 上下文中的字符时通常所说的意思;许多字素簇可以用多个字符序列表示,例如 á 可以编码为 U+0061 U+0301U+00E1 - 这就是规范化的用武之地...

标签: c++ c unicode utf-8 unicode-normalization


【解决方案1】:

这在很大程度上取决于您认为“字母”是什么。

UTF8 基本上是 Unicode 的一小部分。

基本上至少有三个层次:Bytes、Code point 和 Grapheme clusters。 根据某种编码,一个代码点可以编码为一个或多个字节,如 UTF8、UTF16 或 UTF32。这种编码是唯一的(因为所有替代方式都被声明为无效)。然而,代码点并不总是字形,因为存在所谓的组合字符。这种组合字符跟在基本字符之后,正如它们的名字所说,与基本字符组合在一起。例如,组合字符 U+0308 COMBINING DIAERESIS 将分音符 (¨) 放在前面的字母上方。因此,如果它遵循例如a (U+0061 LATIN SMALL LETTER A),结果为 ä。但是,字母 ä 也有一个代码点(U+00E4 拉丁小写字母 A WITH DIAERESIS),因此这意味着代码序列 U+0061 U+0308 和 U+00E4 描述了同一个字母。

因此,每个代码点都有一个有效的 UTF 8 编码(例如,U+0061 是“\141”,U+0308 是“\314\210”,U+00e4 是“\303\244”,但是字母 ä 由代码点序列 U+0061 U+0308(即 UTF8 中的字节序列“\141\314\210”)和单个代码点 U+00E4(即字节序列“\303\244”)编码.

更糟糕的是,由于 Unicode 制造商决定组合字母跟随基本字母而不是在它之前,你无法知道你的字形是否完整,直到你看到 下一个 代码点(如果它不是一个组合代码点,你的信就完成了)。

【讨论】:

  • 很好的答案。基本字母后面可以有多少个组合字母?只有一还是零?
  • +1 在尝试正确获取 unicode 之前,必须将此作为必读内容添加书签。
  • @James:随心所欲。我认为正确的韩文最多可以有三个,梵文也可能需要几个。 Zalgoist 文本有很多。
  • 没有字节,也没有字符。只有代码点和扩展字素簇。 Grapheme_Base 代码点后面的 Grapheme_Extend 代码点数量没有限制。不要太确定你知道这些东西的顺序:考虑带有Logical_Order_Exception 属性的代码点。最后,字素簇 U+000D U+000A 没有组合字符,但是是两个码位。许多多码点字素簇不包含组合字符。无数个没有单个代码点代表的字素簇。正常化或死亡。
  • @tchrist:当然有字节。或者您还会将 UTF8 的 8 位单元称为什么?
【解决方案2】:

有效 UTF-8 确实对每个字符进行了唯一编码。但是,也有所谓的超长序列,它符合一般的编码方案,但根据定义是无效的,因为只有最短的序列可以用来编码一个字符。

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

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

有点离题:这是我想出的一些 ASCII 艺术作品,以帮助可视化 character 的不同概念。垂直分开的是 humanabstractmachine 层次。随意想出更好的名字...

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

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

  • 字节序列有效,即不包含超长序列或编码非字符代码点
  • 字符序列已标准化,因此等效的字素簇具有唯一的表示形式

【讨论】:

    【解决方案3】:

    首先你需要一些术语:

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

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

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

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

    【讨论】:

    • 字形和字符不是一回事。查看 OpenType 的替代风格以获得生动的示例。
    • 我不知道字形是什么。 Unicode 定义了代码点,它定义了扩展的字形簇。其他任何内容都是与标准中的内容不相符的花言巧语,因此不应使用。
    • ninjalj 勉强。 K 范数形式与单例无关。事实上,任何像你提到的那样的单例都会被任何规范化形式破坏。
    • @tchrist: 现在至少可以通过还是我应该完全删除它?
    【解决方案4】:

    是的。 UTF-8 只是编码 Unicode 字符的标准方式。这样做是为了让每个 Unicode 字符只有一种编码方式。

    有点题外话:知道某些字符的外观(与人类)非常相似可能很有用,但它们仍然不同 - 例如,西里尔文中有一个与“/”非常相似的符号.

    【讨论】:

    • 你的意思是像空间和不间断的空间?
    • 不确定 Unicode 中是否有用于不间断空格的字符(我猜有),否则:是的,正是如此。
    • Unicode 没有字符。它具有代码点,并且具有扩展的字形簇。考虑简单的事情,例如 ë 和 ẍ。代码点也不是。两者都是扩展的字素簇。第一个有一个规范化形式,只需要一个代码点,但第二个不需要。
    • 实际上有多个不间断空格。 U+FEFF(BOM)也是一个零宽度的不间断空格。
    • @thrist:我的立场是正确的。但是,出于实际目的,我的答案是正确的 - 其他人似乎已经发布了更深入的答案。 :)
    【解决方案5】:

    是的,有点。如果使用得当,每个 unicode 代码点只能以 UTF-8 的一种方式编码,但这部分是因为要求任何字符只应使用最短的适用 UTF-8 字节序列。

    但是,如果不是出于此要求,用于对字符进行编码的方法可以以不止一种方式对许多字符进行编码 - 尽管不合适,但在某些情况下会这样做。

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-07-11
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多