关于UTF-16

发布时间 2023-04-13 10:59:33作者: nongyi

前言

首先要明确的一点是:UTF-16 是将 Unicode 中的字符编码转换为实际存储形态的实现方式。因此,在了解 UTF-16 之前,先要简单认识一下什么是 Unicode。

Unicode

Unicode 是计算机科学领域中的一项业界标准,包括字符集和编码方案等,目的是为了解决传统字符编码方案的局限性,以满足跨语言、跨平台进行文本转换、处理的要求。

Unicode 的编码范围为 0~0x10FFFF,其中 U+0000~U+FFFF 为基本平面(BMP),常见的字符都映射在此平面;U+010000~U+10FFFF 为辅助平面(SMP),剩下的字符都映射在此平面。

下面还有两个概念会贯穿下文:

  • 码点:一个字符在 Unicode 编码表中对应的一个编码值即为一个码点,一个码点由一个或多个码元组成
  • 码元:即具体编码形式中的最小单位,如 UTF-8 中一个码元为 8 位,UTF-16 中一个码元为 16 位

UTF-16

Unicode 字符集用 0~0x10FFFF 映射字符,而 UTF-16 就是将映射的编码转换为实际存储形态的一种实现方式。UTF 是 Unicode Transfer Format 的缩写,即将 Unicode 转换为某种格式之意。UTF-16 的应用范围十分广泛,Java 和 JavaScript 内部的默认编码方式皆为 UTF-16。

UTF-16 是一种变长编码方式,每个字符编码由 1~2 个码元构成,码元长度为 16 位。其中基本平面的字符为 1 个码元,辅助平面的字符为 2 个码元,也可以说 UTF-16 的编码长度为 2 Byte 或 4 Byte。因此如何判断一个字符是由下一个码元表示还是由下两个码元表示就是下面要关注的一个重要的问题。

编码方式:

在基本平面中有一段空白区间 U+D800~U+DFFF 没有字符的映射关系,这是用来辅助平面字符的,这段区间称为代理区。简单来说,出现了代理区中的码元就意味着字符由下两个码元共同表示,其中前一个码元为高位(H),后一个码元为低位(L)。

设:码点为 U

则:H = ( U - 0x10000 ) / 0x400 + 0xD800,L = ( U - 0x10000 ) % 0x400 + 0xDC00

下面展开介绍算法究竟做了什么

设:码点为 U+1D306
∵ U+1D306 > U+FFFF
∴ 需要由两个码元表示

U+1D306 的二进制表示为:
0001  1101  0011  0000  0110

计算高低位都先减去 0x10000,用二进制表示为:
0001  1101  0011  0000  0110
-
0001  0000  0000  0000  0000
=
0000  1101  0011  0000  0110
即:U+D306

接着,
高位除以 0x400,用二进制表示为:
0000  1101  0011  0000  0110
/
0000  0000  0100  0000  0000
=
0000  0000  0000  0011  0100

再加 0xD800,用二进制表示为:
0000  0000  0000  0011  0100
+
0000  1101  1000  0011  0100
即:U+D834

低位对 0x400 除余,用二进制表示为:
0000  1101  0011  0000  0110
%
0000  0000  0100  0000  0000
=
0000  0000  0011  0000  0110

再加 0xDC00,用二进制表示为:
0000  0000  0011  0000  0110
+
0000  1101  1100  0000  0000
=
0000  1101  1111  0000  0110
即:U+DF06

∴ U+1D306 在内存中的编码为 0xD834 0xDF06,代码中表示为 \uD834\uDF06

我们回过头来对算式过程进行纵向对比不难发现,高低位计算的三个步骤为:

  1. 去溢出位
  2. 平分前后 10 位
  3. 加高低位鉴别

由算式过程反推也可以得出结论:码元高 6 位为 54 的为高位,码元高 6 位为 55 的为低位。

拓展:

  • Java 中的 char 类型描述了 UTF-16 编码中的一个码元
  • JavaScript 中 String.prototype.length 描述的是字符串编码后的码元个数