如何确定UTF-16字符的字节宽度?

3
什么是阅读UTF-16字节流的规则,以确定一个字符占用多少个字节?我已经阅读了标准,但基于对真实世界中UTF-16编码流的经验观察,似乎有些情况下标准不适用(或者我遗漏了标准的某个方面)。
从阅读UTF-16标准https://www.rfc-editor.org/rfc/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 LATIN CAPITAL LETTER A U+0041 00 41 2
Б CYRILLIC CAPITAL LETTER BE U+0411 04 11 2
KATAKANA LETTER SMALL A U+30A1 30 A1 2
RABBIT FACE U+1F430 D8 3D DC 30 4
然而,当将以下ISO 10646字符编码为UTF-16时,它似乎是4个字节,但读取前导2个字节似乎并没有表明它会这么长:
字符 Unicode名称 UTF-16编码(十六进制,大端序) 大小(字节)
⚕️ 埃斯库勒庇斯之杖 26 95 FE 0F 4
虽然我更愿意保持我的问题与软件无关;以下SQL将在Microsoft SQL Server 2019上复现此行为,默认排序规则和默认语言。(请注意,SQL Server是小端序)。
select cast(N'⚕️' as varbinary);
----------
0x95260FFE

很简单,你如何/为什么读取0x2695并认为“我需要读取下一个字来表示这个字符。”?为什么这似乎与已发布的UTF-16标准不一致?

1
请注意,许多“字符”比一个代码点长得多,例如‍‍‍、️‍等。在UTF-16中,它们分别为D83D DC69 200D D83D DC69 200D D83D DC66 200D D83D DC66D83C DFF3 FE0F 200D D83C DF08D83D DC67 D83C DFFB - phuclv
2个回答

4

这一切的正式定义被称为“扩展字符簇”,并在Unicode文本分段报告中进行了定义。正如Joachim Sauer所指出的,Unicode中使用术语“字符”时需要谨慎。

代码点是“U+....”语法所引用的内容,并试图捕获书面语言的“单位”,例如“尖音符号”。但读者所认为的字符(例如带有尖音符号的“e”)是一个“字符簇”,由一个或多个代码点组成。最终呈现到屏幕上的是一个“字形”,它依赖于上下文和字体。

Unicode中的字符簇实际上比这更加微妙。Unicode试图以“中立”的方式来定义它们。(在考虑语言时,“中立”其实并不存在,但Unicode确实在尝试。)例如,在斯洛伐克语中,ch、dz和dž每个都是一个字母,但在Unicode中却被认为是两个字符簇。(试着数一下斯洛伐克单词中的“字母”。有些单词包含字母“dz”,而其他单词则包含字母“d”和字母“z”。哦,人类的书写系统,我是那么地爱你。)

图形簇到字形的映射也是相当复杂的。例如,在阿拉伯语中,单个字形“لا”实际上是两个图形簇,“ل”(阿拉伯语字母拉姆)和“ا”(阿拉伯语字母阿勒芙)。如果您使用鼠标选择字形,则会看到有两个可选择的部分,如果将它们复制并粘贴到另一个窗口中,则会看到它们变成其组成部分。(为了使事情更加复杂,Unicode还为连字定义了单个代码点,即阿拉伯语拉姆与阿勒芙隔离形式:ﻻ。如果您尝试选择其中的一部分,则会发现无法选择。它是一个“字符”。)
您的特定情况有些特殊。变量选择器先于Unicode,并且主要设计用于处理汉字(中文)字符的不同变体。但是,与Unicode的每个功能一样,它最终主要用于表情符号。VS-16是“表情符号”的表示形式。最著名的例子是红心,它是HEAVY BLACK HEART ❤,后跟VS-16:❤️。
同样,您的字符U+2695 STAFF OF AESCULAPIUS是一个单一的代码点,默认情况下(文本样式)看起来像这样:⚕。当您添加VS-16时,它以“表情符号风格”呈现:⚕️。在某些方面,它是相同的“字符”。或者说它不是?这取决于您使用它的目的。
表情符号风格通常稍大,并居中于其块中,有时会添加颜色。请注意每种情况下针后面的句点绘制的位置(第二个示例中没有额外的空格;字形只是更宽)。
还有其他组合系统:
- U+0031:1 - U+0031 U+20e3:1⃣(+ COMBINING ENCLOSING KEYCAP,默认文本样式) - U+0031 U+20e3 U+fe0f:1⃣️(+ VARIATION SELECTOR-16,表情符号风格)
所有这些都早于Unicode。现代表情符号要复杂得多,包括自己的几个组合系统(包括目前仅用于国旗的两个系统)。
幸运的是,对于您实际的问题,您的妻子是正确的,您通常可以消耗所有标记为“组合”的尾部代码点以形成扩展的字形簇,并且在某些足够宽泛的“字符”定义中,那是一种“字符”。

1
我将把这个标记为答案,因为它展示了比我的原始简短的“勾选框”答案更好的经验、例子和显然丰富的经验(/痛苦)。虽然在写这篇文章时我已经意识到了我的困境的根源,但是到那时为止,我已经拥有了15分钟阅读所提供的比较知识。非常感谢您花时间制作这个显然专业的答案;它帮助我们通过一个复杂的主题给出了非常直接的方向,而不仅仅是知道“为什么会出错”;) - Rab

1
你的所有断言都是正确的;你对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问题要发射了。我的妻子走进了我的办公室; 在看过我的肩膀后,她对我的耳语道,“你知道组合字符是一件事情,对吧?”。然而,我仍然问了这个问题并回答了它自己,以防我的妻子的甜言蜜语帮助社区的其他成员。


1
另一个很好的例子,说明在谈论Unicode时,“字符”是一个危险的词语。我会使用“字形簇”来代替用户通常理解为字符的内容,“Unicode码点”来指代U+ xxxx所标识的内容。 - Joachim Sauer
确实,“代码点”的细微差别是我必须说是新的。我可能在过去读到过这个术语,并认为它意味着字符,而字符是静态的。只有在进一步阅读后,我才意识到自己对这个主题是多么的幼稚。 - Rab

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