跳转到内容

2. Unicode 和 UTF-8

为了统一全世界各国语言文字和专业领域符号(例如数学符号、乐谱符号)的编码,ISO 制定了 ISO 10646 标准,也称为 UCS(Universal Character Set)。UCS 编码的长度是 31 位,可以表示 231 个字符。如果两个字符编码的高位相同,只有低 16 位不同,则它们属于一个平面(Plane),所以一个平面由 216 个字符组成。目前常用的大部分字符都位于第一个平面(编码范围是 U-00000000~U-0000FFFD),称为 BMP(Basic Multilingual Plane)或 Plane 0,为了向后兼容,其中编号为 0~256 的字符和 Latin-1 相同。UCS 编码通常用 U-xxxxxxxx 这种形式表示,而 BMP 的编码通常用 U+xxxx 这种形式表示,其中 x 是十六进制数字。在 ISO 制定 UCS 的同时,另一个由厂商联合组织也在着手制定这样的编码,称为 Unicode,后来两家联手制定统一的编码,但各自发布各自的标准文档,所以 UCS 编码和 Unicode 码是相同的。

有了字符编码,另一个问题就是这样的编码在计算机中怎么表示。现在已经不可能用一个字节表示一个字符了,最直接的想法就是用四个字节表示一个字符,这种表示方法称为 UCS-4 或 UTF-32,UTF 是 Unicode Transformation Format 的缩写。一方面这样比较浪费存储空间,由于常用字符都集中在 BMP,高位的两个字节通常是 0,如果只用 ASCII 码或 Latin-1,高位的三个字节都是 0。另一种比较节省存储空间的办法是用两个字节表示一个字符,称为 UCS-2 或 UTF-16,这样只能表示 BMP 中的字符,但 BMP 中有一些扩展字符,可以用两个这样的扩展字符表示其它平面的字符,称为 Surrogate Pair。无论是 UTF-32 还是 UTF-16 都有一个更严重的问题是和 C 语言不兼容,在 C 语言中 0 字节表示字符串结尾,库函数 strlenstrcpy 等等都依赖于这一点,如果字符串用 UTF-32 存储,其中有很多 0 字节并不表示字符串结尾,这就乱套了。

UNIX 之父 Ken Thompson 提出的 UTF-8 编码很好地解决了这些问题,现在得到广泛应用。UTF-8 具有以下性质:

  • 编码为 U+0000~U+007F 的字符只占一个字节,就是 0x00~0x7F,和 ASCII 码兼容。
  • 编码大于 U+007F 的字符用 2~6 个字节表示,每个字节的最高位都是 1,而 ASCII 码的最高位都是 0,因此非 ASCII 码字符的表示中不会出现 ASCII 码字节(也就不会出现 0 字节)。
  • 用于表示非 ASCII 码字符的多字节序列中,第一个字节的取值范围是 0xC0~0xFD,根据它可以判断后面有多少个字节也属于当前字符的编码。后面每个字节的取值范围都是 0x80~0xBF,见下面的详细说明。
  • UCS 定义的所有 231 个字符都可以用 UTF-8 编码表示出来。
  • UTF-8 编码最长 6 个字节,BMP 字符的 UTF-8 编码最长三个字节。
  • 0xFE 和 0xFF 这两个字节在 UTF-8 编码中不会出现。

具体来说,UTF-8 编码有以下几种格式:

markdown
U-00000000 – U-0000007F: 0xxxxxxx
U-00000080 – U-000007FF: 110xxxxx 10xxxxxx
U-00000800 – U-0000FFFF: 1110xxxx 10xxxxxx 10xxxxxx
U-00010000 – U-001FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
U-00200000 – U-03FFFFFF: 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
U-04000000 – U-7FFFFFFF: 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx

第一个字节要么最高位是 0(ASCII 字节),要么最高两位都是 1,最高位之后 1 的个数决定后面有多少个字节也属于当前字符编码,例如 111110xx,最高位之后还有四个 1,表示后面有四个字节也属于当前字符的编码。后面每个字节的最高两位都是 10,可以和第一个字节区分开。这样的设计有利于误码同步,例如在网络传输过程中丢失了几个字节,很容易判断当前字符是不完整的,也很容易找到下一个字符从哪里开始,结果顶多丢掉一两个字符,而不会导致后面的编码解释全部混乱了。上面的格式中标为 x 的位就是 UCS 编码,最后一种 6 字节的格式中 x 位有 31 个,可以表示 31 位的 UCS 编码,UTF-8 就像一列火车,第一个字节是车头,后面每个字节是车厢,其中承载的货物是 UCS 编码。UTF-8 规定承载的 UCS 编码以大端表示,也就是说第一个字节中的 x 是 UCS 编码的高位,后面字节中的 x 是 UCS 编码的低位。

例如 U+00A9(© 字符)的二进制是 10101001,编码成 UTF-8 是 11000010 10101001(0xC2 0xA9),但不能编码成 11100000 10000010 10101001,UTF-8 规定每个字符只能用尽可能少的字节来编码。