12 August 2015

Unicode和UTF-8是两种完全不同的东西。本文除了介绍它们,还会解释UCS、UTF-16、UTF-32等常见的名词。

Unicode标准

Unicode是一个工业标准,名为“The Unicode Standard”,它与国际标准“ISO/IEC 10646”一样,都是为了对计算机中字符的使用做规范。 Unicode标准的制定者是一个称为“Unicode财团”的工业组织,ISO/IEC 10646的制定者则是国际标准化组织(ISO)。 这两个机构从1991年左右发布第一版标准后,就决定在字符集和编码方法这些基本部分互相同步以保持一致。除这些之外,Unicode还包括文字校对、文本归一化、 双向书写等方面的规定,比ISO/IEC 10646涉及的范围要大一些。本文只介绍字符集和编码相关的内容。

Unicode和ISO/IEC 10646规定了一个通用字符集(Universal Character Set,UCS),其中包含了所有人类语言的全部字符。 UCS中的每个字符都分配一个数字编号,这些数字称为码位(code point)。码位是码空间(code space)中的点。 整个码空间分为128个组,每个组分为256个平面,每个平面分为256个行,每个行分为256个单元。如果用二进制来表示,码空间其实是一个31位无符号整数能表示的数字的集合,即0~7FFFFFFF(码位常用十六进制表示,下同)。

这个码空间是如此之大,人类语言文字中的全部符号只占用其中很小一部分。例如,码空间有128*256个平面,但最初的标准只用了其中第一个平面,命名为基本多语言平面(Basic Multilingual Plane,BMP)。 后来,随着更多的字符加入,一个平面不够用了,就开始启用别的平面。但是,再怎么用也用不了多少。标准已经承诺,以后的字符添加将仅限于前17个平面,也就是只会用到0~0x10FFFF这个范围的码位。 另外,即使是前17个平面甚至BMP内,也并非每个码位都对应的有字符。有些码位是用于特殊作用的,有些码位并没有被利用。

不管怎样,每个字符都会有一个标准的编号,即码位,从它就可以找到对应的字符。接下来要考虑的就是在计算机里如何用字节流来表示这些字符的码位,也就是字符编码的问题。

UTF规范

Unicode标准中规定的字符编码称为Unicode转换格式(Unicode Transformation Format,UTF),包括UTF-8、UTF-16、UTF-32。 这里所谓的转换,就是在码位与存储或传输的字节流之间互相编码解码。

在介绍UTF之前,我们先介绍一下UCS-2和UCS-4。这都是早期出现在ISO/IEC 10646中的字符编码标准,现在已经被淘汰了。 前面提到,最早的标准只用了BMP,也就是说码位的范围是0~0xFFFF(共65536个;嗯,可能那时觉得不会有那么多字符)。于是标准提出了一个十分简单直接的编码方法,即所有码位都统一用一个2字节的无符号整数表示,这就是UCS-2。 后来加入了更多的字符,两字节不够了,标准又简单粗暴地改为统一用4个字节来表示一个字符,这就是UCS-4。 这样的定长编码固然简单,但编码的效率却比较低,也就是说每个字符所需的数据量太大了。下面我们会看到,UTF将会用变长编码来减少每个字符所需的字节数。

在UTF系列中,UTF-32其实就是UCS-4,采用32位(=4字节)定长编码来表示一个字符。它现在也只是用在一些内部特殊场合,很少用于存储或传输的字符编码了。 UTF-16也是定长编码,每个字符固定占用16位(2个字节)。但它通过一些特殊处理来使得两字节能够表示0~0x10FFFF这个范围的码位,因此取代UCS-2满足了BMP之外新加入字符的要求。 UTF-8则提出了一种的变长编码方法,一个字符最少可以用1个字节表示。它不仅提高了编码效率,还跟计算机行业的元老级标准ASCII(单字节编码)完全兼容。 这使得UTF-8脱颖而出,成为了现在最流行的字符编码方法。

接下来我们就着重介绍一下UTF-8是如何编码字符的。

UTF-8编码方法

UTF-8的设计能够支持编码整个码空间(0~7FFFFFFF),虽然实际会用到的只有前17个码平面(0~0x10FFFF)。下表列出了对整个码空间的UTF-8编码方法。

码位范围(十六进制)          UTF-8编码格式(二进制)
0000 0000 ~ 0000 007F     0xxxxxxx
0000 0080 ~ 0000 07FF     110xxxxx 10xxxxxx
0000 0800 ~ 0000 FFFF     1110xxxx 10xxxxxx 10xxxxxx
0001 0000 ~ 001F FFFF     11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
0020 0000 ~ 03FF FFFF     111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
0400 0000 ~ 7FFF FFFF     1111110x 10xxxxxx ... 10xxxxxx

第一列是码位的范围,第二列是这个范围的码位的编码格式。每一行中的码位范围所需要的比特数恰好等于其对应的编码格式中x的个数。 编码时只需要将码位转换为二进制,然后按照表中所示的格式填充x即可。

例如,欧元符号€的码位是U+20AC,在第三行的范围内。因此它需要三个字节编码,需填充16个x。 0x20AC的16位二进制是0010 0000 1010 1100,将这16个比特填充到x的位置,就得到11100010 10000010 10101100。这就是欧元符号的UTF-8编码。

当然,UTF-8的设计保证了解码的可行性,也就是能够从一个字节流中唯一的解码出每个字符。解码时,只需要按照表中所示编码格式进行识别和转换即可。

计算机行业的国际化使得统一的字符编码成为趋势。Unicode字符集和UTF-8编码是行业的主流标准,我们应该顺势而为,不遗余力地支持。 国内也有自己的字符编码标准(如GB2312、GB10800、BIG5等),但该舍弃还是舍弃吧。吾爱吾国,但吾更爱科技无国界。