问题描述
读取 UTF-16 字节流的规则是什么,以确定一个字符占用多少字节?我已经阅读了这些标准,但是根据对现实世界 UTF-16 编码流的经验观察,看起来标准在某些地方不成立(或者我缺少标准的某个方面) .
从阅读UTF-16标准https://tools.ietf.org/html/rfc2781:
前 2 个字节的值 | 结果字符长度(字节) |
---|---|
0x0000-0xC7FF |
2 |
0xD800-0xDBFF |
4 |
0xDC00-0xDFFF |
无效序列(RFC2781 2.2.2) |
0xDFFF-0xFFFF |
4 |
在实践中,至少在某些情况下,这似乎是正确的。使用临时 sql 脚本(sql Server 2019;UTF-16 排序规则),但也使用在线解码器进行了验证:
人物 | Unicode 名称 | ISO 10646 | UTF-16 编码(十六进制,大端) | 大小(字节) |
---|---|---|---|---|
A | 拉丁文大写字母 A | U+0041 | 00 41 |
2 |
Б | 西里尔大写字母 BE | U+0411 | 04 11 |
2 |
ァ | 片假名小写 A | U+30A1 | 30 A1 |
2 |
? | 兔脸 | U+1F430 | D8 3D DC 30 |
4 |
然而,当将以下 ISO 10646 字符编码为 UTF-16 时,它似乎是 4 个字节,但读取前导 2 个字节似乎没有表明它会这么长:
虽然我宁愿让我的问题与软件无关;以下 sql 将使用默认排序规则和默认语言在 Microsoft sql Server 2019 上重现此行为。 (注意 sql Server 是小端)。
select cast(N'⚕️' as varbinary);
----------
0x95260FFE
很简单,您如何/为什么阅读 0x2695
并认为“我需要为这个字符阅读下一个单词。”?为什么这似乎不符合已发布的 UTF-16 标准?
解决方法
所有这些的正式定义称为“扩展字素簇”,它在 Unicode Text Segmentation report 中定义。正如 Joachim Sauer 指出的那样,谨慎使用 Unicode 中的“字符”一词是明智的。
代码点是“U+....”语法所指的内容,并试图捕获书面语言的“单位”,例如“重音”。但是读者会想到一个字符(例如“带有重音的 e”)是一个“字素簇”,它由一个或多个代码点组成。最终呈现在屏幕上的是一个“字形”,它既依赖于上下文,也依赖于字体。
Unicode 中的字素簇实际上比这更微妙。 Unicode 尝试以“中立”的方式定义它们。 (在考虑语言时确实没有“中性”这样的东西,但 Unicode 确实尝试过。)例如,在斯洛伐克语中,ch、dz 和 dž 都是一个字母,但在 Unicode 中被认为是两个字素簇。 (尝试数一数斯洛伐克语单词中的“字母”。有些单词包含字母 dz,有些单词包含字母 d 后跟字母 z。哦,人类的书写系统。我非常爱你。)
字形簇到字形的映射也很复杂。例如,在阿拉伯语中,单个字形 لا 实际上是两个字素簇, ل (ARABIC LETTER LAM) 后跟 ا (ARABIC LETTER ALEF)。如果您使用鼠标选择字形,您会看到有两个可选择的部分,如果您将它们复制并粘贴到另一个窗口,您会看到它们转换为它们的组成部分。 (只是为了让事情变得更复杂,Unicode 还 为连字定义了一个单一的代码点,ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM:ﻻ。如果您尝试选择其中的一部分,您会发现你不能。这是一个“字符”。)
你的具体情况有点特殊。变体选择器早于 Unicode,主要设计用于处理汉(中文)字符的不同变体。但是,与每个 Unicode 功能一样,它最终主要用于表情符号。 VS-16 是“表情符号”呈现形式。最著名的例子是红心,即HEAVY BLACK HEART ❤,其次是VS-16:❤️。
同样,你的角色 U+2695 STAFF OF AESCULAPIUS 是一个单一的代码点,默认情况下它看起来像这样(文本样式):⚕。当您添加 VS-16 时,它会以“表情符号样式”呈现:⚕️。在某些方面,它是相同的“性格”。或者是吗?取决于您使用它的目的。
表情符号样式通常稍大一点,并在其块中居中,有时会添加颜色。请注意在每种情况下绘制五线谱之后的句点(第二个示例中没有多余的空格;字形只是更宽)。
还有其他组合系统:
- U+0031:1
- U+0031 U+20e3:1⃣(+组合封闭键帽,默认文字样式)
- U+0031 U+20e3 U+fe0f:1⃣️(+ VARIATION SELECTOR-16,表情符号风格)
所有这些都早于 Unicode。 Modern emoji is dramatically more complicated,并包括几个自己的组合系统(包括两个目前仅用于标志的系统)。
但幸运的是,对于您的实际问题,您的妻子是正确的,您通常可以使用所有标记为“组合”的尾随代码点以形成扩展的字素簇,这对某些人来说是一种“字符” “性格”的定义足够宽泛。
,你所有的断言都是完全正确的;您对 UTF-16 标准的解释是正确且完整的。
然而,在您的经验观察中,您假设您只有一个角色。实际上,您已经遇到了 Unicode 实现的细微差别。您的“角色”实际上是两个(尽管是技术上的,而不是视觉上的):U+2695 "STAFF OF AESCULAPIUS" 后跟 U+FE0F "VARIATION SELECTOR-16"。第二个字符是一个非间距标记,它与基本字符结合,用于呈现字符变体。
这会产生字节序列 26 95 FE 0F
,但是您注意到这两个词都不属于 UTF-16 保留扩展字符范围。但这是因为它们都不需要 UTF-16 4 字节扩展。它们被简单地归类为两个离散的 Unicode 字符。
来自 ISO 10646:通用编码字符集 (UCS) 中的 7.9 组合标记:、
组合标记是 Unicode 标准中的一类特殊字符,它们是 旨在与前面的字符组合,称为它们的基数。
组合标记通常具有可见的字形形式……组合标记可能以各种方式与相邻字符进行图形交互。
http://unicode.org/L2/L2010/10038-fcd10646-main.pdf
解释为什么我要回答我自己的问题;我已经准备好了我的 SO 问题。我的妻子走进我的办公室;看着我的肩膀后,她在我耳边低声说:“你知道组合字符是一回事,对吧?”。然而,我仍然提出了这个问题并自己回答了这个问题,以防我妻子的甜言蜜语能帮助社区的另一位成员。