谈编码与Unicode标准

项目过程中,又被编码问题搞得晕头转向。于是决定好好弄明白编码问题。

一、理解编码问题

之所以要编码,是因为计算机只能处理包含0或1的二进制数据。为了电脑能够存储、处理和传输文字,便需要编码来将文字转换为二进制数据。

Unicode和UTF-8是很不一样的,Unicode可以理解为确定了哪些字符需要被编码,然后给所有字符一个编号。这个编号是不会直接存储到内存或者磁盘中的。UTF-8是负责具体的编码工作的,它会将Unicode中所有定义的字符,根据他们的编号编码成二进制串。所以在看到说某文本是Unicode编码时,他的意思应该是指Unicode系列的某个编码:UTF-8、UTF-16或者UTF-32。

Unicode标准是及其复杂的,这种复杂性的来源之一是Unicode需要能够用于世界上所有语言的编码。而因为个别语言的一些特殊性质,就使得Unicode需要设计一些机制来完成对个别语言的编码。另外,Unicode的设计并不能够抛开过去设计的编码方案全部重新来过。Unicode的设计需要对现有的编码方式、现有的系统进行一定程度的妥协。

另外,Unicode解决的基本只有编码问题,除了编码以外、还有字符的显示、编程语言对编码的支持等问题需要另外研究。

二、字符编码的历史

字符编码问题在计算机出现之前就存在了。电报系统中使用的摩尔斯电码就是一种字符编码方案,发明于1835年。由于电报传入中国,在1873年中国也设计了自己的编码方案[1]。

这里写图片描述

ASCII编码规范随着计算机的发展于1967年首次发表。由于当时计算机普及程度的局限,ASCII编码仅能对现代英语进行编码。后来西欧国家也要用啊,于是就有了EASCII(Extended ASCII)。这还只是对ASCII做了扩展而已,后来中国也开始用计算机了。于是在1918年,中国国家标准总局制定了自己的中文编码方案——GB 2312 [2]。

不同地区都试图设计对自己地区语言支持最好的编码方案。如此以来,全世界出现了上百种编码方案。这产生了很大的问题。按照我自己的理解,我认为问题是这样的。在使用不同编码的系统之间,都需要进行编码的转换。然而由于每种编码都只能表示全世界中的某些语言的字符,因而在从编码A转换为编码B的过程中,就会出现编码A的某个字符不能用编码B表示,因此无法转换的错误。必须小心的检查数据才能避免这个问题。除此以外,对软件开发者而言,开发支持多种语言的软件也变得非常复杂,因此开发者会倾向于开发只支持一种语言的程序。这影响了软件在全世界的使用。

对于字符编码不统一产生的问题,可以参考Unicode文档1.2节[3]中的陈述:

The Unicode Standard began with a simple goal: to unify the many hundreds of conflicting ways to encode characters, replacing them with a single, universal standard. The pre-existing legacy character encodings were both inconsistent and incomplete—two encodings could use the same codes for two different characters and use different codes for the same characters, while none of the encodings handled any more than a small fraction of the world’s languages. Whenever textual data was converted between different programs or platforms, there was a substantial risk of corruption. Programs often were written only to support particular encodings, making development of international versions expensive. As a result, developing countries were particularly hard-hit, as it was not economically feasible to adapt specific versions of programs for smaller markets. Technical fields such as mathematics were also disadvantaged, because they were forced to use special fonts to represent arbitrary characters, often leading to garbled content.

综上所述,世界需要一个统一的编码方案。

其实研究者们在80年代就试图建立世界通用的编码方案。然而他们面临着一个两难的问题。一方面,由于字符数量的增加使得不得不增加使用的比特数来使得编码方案囊括世界的所有字符。另一方面,对于使用字符数量少的拉丁字符的西方国家来说,更多的比特数会增加在当时而言稀少而昂贵的计算资源。[4]

最终在1991年,Unicode用一种新思路打破了僵局。使得编码统一成为可能。

三、Unicode

Unicode的第一个版本诞生于1991年。Unicode的最特别之处在于,打破了自电报时代起所固有的传统思路,即每个字符必须直接和一串比特序列进行对应。在这样的直接对应关系中间增加了一个新的层次:码点(code point),这是一个整数,每个字符都有唯一的一个整数与之对应。因此,字符首先对应到码点,然后码点再和对应的二进制编码对应。在我看来,这样的方法类似网络层级结构,是一个将大问题化为几个小问题的聪明的办法。

简略的来说,Unicode编码的内容其实很简单:第一步,对于全世界需要编码的字符,每个字符分配一个整数(码点)作为字符的ID。第二步,将码点对应到二进制串上。

当然实际上这个问题要复杂的多,有许多细节的问题需要考虑。这里简要的列举一下,上述第一步中,编码遇到的问题和Unicode的解决办法:

1、组合字符
一些语言中的一个字,是由一些基本的部分组合而成的,比如韩语。另外,像法语中的变音符号,可以和不同的字母进行组合。像这些由基本单元组合而成的字符,如果把每种可能的组合单独作为一个字符,给与一个码点。对整个编码的空间是一种浪费。因此在Unicode中,对一些语言的组成基本单元进行了编码,而这些语言中的字符,可能是由多个这样的基本单元组成的。

2、字典排序
英文字典中,词语是按字母顺序排列的,具体说,字母a开头的排在最前面,z开头的字母排在最后。新华字典中,中文字是按照拼音的字母顺序排列的。根据Unicode是否能得到字符的字典排序信息呢?答案是可以。对每种语言下的字符,Unicode中都定义了每个字符的字典排序权重。这个权重作为字符的一个属性,在Unicode标准中有定义。

所以,Unicode是一个包含内容广泛的标准,每个字符,不仅有对应的码点,还有名称、图例、以及其他各种属性。这些属性中记录了字符的全部信息。用于控制格式的特殊符号的语义也是作为属性存储在字符中的。

3、字符与字形
Unicode标准只定义抽象的字符,而不定义字符具体的字形。这说明了Unicode标准内容的范围。同一个汉子,无论是楷书、行书还是草书写的,都是同一个字符。一个字符的具体外形如何,不是Unicode的管理范围。这条规则其实是显而易见的。不过也有一些特殊情况:在阿拉伯语中,一个字母被放在词的开头,中间和结尾处形状有很大差别。过去一些编码规范将这些不同外形的字符当成不同的字符进行编码。在Unicode中,一个阿拉伯字母只对应一个码点,无论它放置的位置,外形的变化交给了文字渲染的有关程序进行处理。

4、从左至右、从右至左 以及 两者交替。
有的语言是从左往右写的,有的语言是从右往左写的,比如阿拉伯语、希伯来语。默认的,对于每种语言,Unicode标准是依照人们阅读该语言的方向存储该语言的文本的。然而,还有更复杂的情况是两种不同书写方向的语言混合在一起构成的文本。为了解决这个问题,Unicode中引入了特殊字符,用来指示文本的书写方向。

5、统一性与兼容性
统一性是指一个字符,只能有唯一的编码方式。这似乎是显而易见的。然而为了保证编码的兼容性,部分的,牺牲了统一性。举例来说,Unicode中定义了组合字符,但是为了和其他的编码兼容,不得不把本来应该通过组合的方式构成的字符作为一个整体,加入到了Unicode中成为一个字符。

总的来说,Unicode标准是一个非常复杂的内容。并且它也不是一个完美的标准。想深入学习的建议阅读Unicode官方文档[5]。

四、UTF-8

上一节中将Unicode编码分为两步:第一步,对于全世界需要编码的字符,每个字符分配一个整数(码点)作为字符的ID。第二步,将码点对应到二进制串上。这一节着重讲第二步的内容:

Unicode定义的码点的取值范围从 0 到 10FFFF(16进制),这个范围被称为code space(编码空间)。根据编码空间的大小,可以确定至少需要21比特才能编码所有的Unicode字符。在Unicode标准中,定义了3种编码方案,分别被称为:UTF-8、UTF-16和UTF-32。

UTF是 Unicode Transformation Format 的简称。意思是,Unicode虽然定义了每个字符到一个整数的映射,但是这个整数怎么存储、传输和编码就在UTF中进行定义。在UTF-8中,用8bit作为一个基本的code unit。每个字符的编码长度,都会是code unit的整数倍。因此UTF-8中,字符的编码可能是8比特、16比特、24比特或者32比特。同理,UTF-16中code unit 长度是16比特,可能的编码长度是16比特或者32比特。UTF-32中code unit 长度是32比特,所有字符的编码都是32比特。

下面详细说明UTF-8的编码方案:

根据码点的大小,UTF-8将码点处于不同范围中的字符编码为了1-4个字节长度的二进制串。具体如下[6]:
这里写图片描述

上图x表示用于存储码点数据的位。

单字节的编码,0开头,可以放置7位二进制数据。
n个字节的编码,第一个字节用n个1加一个0开头,后面的字节用10开头。其余位置放置码点的二进制数据。

可以看到,使用4个字节时,正好可以编码21位的数据,因此4字节足够编码所有Unicode字符。

UTF-8有许多优良的性质,简述如下:
1、没有大小端存储的问题:UTF-8的每个code unit都是一个字节,因此没有大小端存储问题。在UTF-16中,一个code unit 大小两个字节,其中一个字节存储高位数据,另一个存储低位数据。当存储到内存或者磁盘时,是存储高位数据的字节在前还是存储低位数据的字节在前,分别对应了大端和小端两种存储方式。
2、有明显的字符边界:由于UTF-8中有前导码,可以清楚的看出哪几个字节是一同编码一个字符的。当某些字节出错时,也只会影响到它所属的字符的编码,而对其他字符不会有影响。
3、对ASCII编码的兼容:UTF-8中使用一个字节进行编码的内容和ASCII编码是一样的。UTF-8对ASCII兼容,使得原本使用ASCII编码的程序,可以轻松的改用UTF-8编码。

参考资料:
[1] 中文电码 - 维基百科
[2] GB 2312 - 维基百科
[3] Unicode Standard v10 Chapter 1.2
[4] Character encoding - 维基百科
[5] Unicode Standard v10
[6] UTF-8 - 维基百科

猜你喜欢

转载自blog.csdn.net/vincent_hbl/article/details/79521627