计算机中最常处理的数据信息除了数值数据外,还有我们人类交流的语言,也就是文字。所谓文字就是一些记录信息的图像符号,也称字符。因此字符信息在计算机中的表示是不可缺少的。然而,不同的国家和民族所使用的字符各不相同,计算机中是如何来表示这些不同的字符呢?
在计算机中是通过字符编码的方式来表示这些字符的。计算机中储存的信息都是用二进制数表示的,而我们在屏幕上看到的英文、汉字字符是二进制转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如"a"用什么表示,称为编码;反之,将存储在计算机中的二进制数解析显示出来,称为解码,如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致“a”解析成“b”或者乱码。
所以,字符编码就是一些约定,使用指定的机器整数的集合来表示某个字符集,其中每个机器整数代表一个字符,从而实现字符信息的传递和记录。并且,不同的编码的取值范围不同,所能表示的字符集也有所不同。
ASCII码
ASCII码(American Standard Code for Information Interchange)即美国标准信息码,一种使用7bit或8bit二进制位进行编码的方案。ASCII码于1961年被提出,用于在不同计算机硬件和软件系统中实现数据传输标准化,大多数的小型机和全部的个人计算机都使用此码。该编码后来被国际标准化组织(ISO)采纳而成为一种国际通用的信息交换标准代码,即国际5号码。ASCII码最初采用7bit进行编码,一共有2^7 (128)种编码,从00000000到11111111可以表示128个不同的字符。现扩充为8bit编码,最多可以给256个字符(包括字母、数字、标点符号、控制字符及其他符号)分配或指定数值。所以ASCII码划分为两个集合:128个字符的标准ASCII码和附加的128个字符的扩充ASCII码。
基本的ASCII字符集共有128个字符。对应的ISO标准为ISO646标准。这128个字符又可以分为两类:可显示/打印字符95个和控制字符33个。所谓可显示/打印字符是指包括0~9十个数字符,a~z、A~Z共52个英文字母符号,“+”“-”“≠”“/”等运算符号,“。”“?”“,”“;”等标点符号,“#”“%”等商用符号在内的95个可以通过键盘直接输入的符号,它们都能在屏幕上显示或通过打印机打印出来。控制字符是用来实现数据通信时的传输控制、打印或显示时的格式控制,以及对外部设备的操作控制等特殊功能。这33个控制字符都是不可直接显示或打印(即不可见)的字符。如编码为7F的DEL用作删除操作,编码为07的BEL用作响铃控制等。
其中,字母和数字的ASCII码的记忆是非常简单的。我们只要记住了一个字母或数字的ASCII码(例如记住A的ASCII码为65,0的ASCII码为48),知道相应的大小写字母之间差32,就可以推算出其余字母、数字的ASCII码。
虽然标准的ASCII码是7位编码,但由于计算机基本处理单位为字节(1Byte=8bit),所以一般仍以一个字节来存放一个ASCII字符。每一个字节中多余出来的一位(最高位)在计算机内部通常保持为0(在数据传输时可用作奇偶校验位)。
由于标准ASCII字符集数目有限,在实际应用中往往无法满足要求。为此,ISO又制定了ISO2022标注,它规定了在保持与ISO646兼容的前提下,将ASCII字符集扩充为8位代码的统一方法。ISO陆续制定了一批适用于不同地区的扩充ASCII字符集,每种扩充ASCII字符集分别可以扩充128个字符,这些扩充字符的编码均为高位为1的8位代码(即十进制数128~255),我们称之为扩展ASCII码。
Unicode码
1.Unicode码概述
最初,计算机技术起源和发展主要在欧美发达国家,这使得ASCII码非常流行。ASCII码对英语来说简单实用,但是无法表示其他国家的语言字符。随着计算机实用的普及,ASCII码已经不能满足全世界其它语言国家的需求。于是Unicode编码字符集应运而生。
Unicode码中文名叫万国码,创立之初(1991年)便是希望能够定制一套可以容纳所有文字、符号的集合。最初,Unicode码被设计为16bit的字符编码。Unicode码的基本思路是将每个字符或符号赋予一个永久、唯一的16位值,叫作码点。值得初学者注意的是,最初的Unicode码和ASCII码的第一个区别是Unicode码规定每个字符长度为16位,而ASCII码只有8.这样Unicode码一共能够提供65536个码点,为Unicode码容纳更多语言字符提供了可能,其中就包括了汉字的编码。
然而,这样的16bit的编码所能产生的字符数65536虽然比ASCII码的256有很大的增加,但是还不足以表示世界上所有的字符。因此Unicode码后又进行了扩展,从0开始,一直到0x10FFFF(以“ox”开头的数代表使用十六进制计数)共1114112个数字。每一数字映射一个符号。现在把[0,0x10FFFF]这个大区段等分成17个小区段,每个区段有65536个数字。这些区段就叫作平面,共有17个平面。然而目前只用了少数平面。
平面 | 始末字符值 | 中文名称 | 英文名称 |
0号平面 | 0x0000~0xFFFF | 基本多文种平面 | Basic Multilingual Plane,简称BMP |
1号平面 | 0x10000~0x1FFFF | 多文种补充平面 | Supplementary Multilingual Plane,简称SMP |
2号平面 | 0x20000~0x2FFFF | 表意文字补充平面 | Supplementary Ideographic Place,简称 SIP |
3号平面 | 0x30000~0x3FFFF | 表意文字第三平面(未正式使用) | Tertiary Ideographic Place,简称TIP |
4号平面至13号平面 | 0x40000~0xDFFFF | (尚未使用) | |
14号平面 | 0xE0000~0xEFFFF | 特别用途补充平面 | Supplementary Special-purpose plane,简称SSP |
15号平面 | 0xF0000~0xFFFFF | 保留作为私人使用区(A区) | Private Use Area-A,简称 PUA-A |
16号平面 | 0x100000~0x10FFFF | 保留作为私人使用区(B区) | Private Use Area-B,简称 PUA-B |
在Unicode5.0.0版本中,已定义的码位只有238605个,分布在平面0、平面1、平面2、平面14、平面15、平面16上。其中平面15和平面16上只是定义了两个各占65534个码位的专用区(Private Use Area),分别是0xF0000~0xFFFFD和0x100000~0x10FFFD.所谓专用区,就是保留给大家放自定义的区域,可以简写为PUA。
平面0也有一个专用区:0xE00~0xF8FF,有6400个码位。平面0的0xD800~0xDFFF,共2048个码位,是一个呗称作代理区(Surrogate)的特殊区域。用于和UTF-16之间的转换,该部分内容在下面讲述UTF-16的时候会详细介绍。
如前所述在Unicode 5.0.0版本中,238605-65534*2-6400-2048=99089,余下的99089个已定义码位分步在平面0、平面1、平面2和平面14上,它们对应着Unicode定义的99089个字符,其中包括71226个汉字。平面0、平面1、平面2和平面14上分别定义了52080、3419、43253和337个字符,平面2上的43253个字符都是汉字,平面0上定义了27973个汉字。
比如,在Unicode中,汉字“字”对应的数字是23383(十进制),十六进制表示为0x5B57.在Unicode中,我们有很多方式将数字23383表示成程序中的数据,包括:UTF-8、UTF-16、UTF-32.UTF是“Unicode Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。
事实上,现在的计算机中的字符和字符串,并不是直接以Unicode码存储的,而是使用上述UTF-8、UTF-16、UTF-32三种方式进行转换后再存储。原因是直接使用Unicode码会导致较严重的效率问题。举例说明:
Unicode的最小编码是+0x00.而最大值是+0x10FFFF,长度不确定。所以在计算机存储的时候,就需要确定多少位表示一个符号。比如说字符串“a中”,它的Unicode码是0x00614E2D,换算成二进制就是0000 0000 0110 0001 0100 1110 0010 1101,假设我们采用16位(2Byte)表示一个Unicode符号,那么在计算机中,符号a就要存储成“0000 0000 0110 0001”,这样的情况中文还好,但是英文就会浪费存储空间。尤其当数据需要在网络上传输或者需要压缩、数据库处理的时候都需要越小存储体积越好的,所以当前计算机中不直接存储Unicode码。
2.编码实现方式
(1)UTF-8编码
UTF-8以字节为单位对Unicode进行编码。从Unicode到UTF-8的编码方式如下所示。
Unicode编码(十六进制) | UTF-8字节流(二进制) | 占用字节数 |
00000000-0000007F | 0xxxxxxx | 1 |
00000080-000007FF | 110xxxxx 10xxxxxx | 2 |
00000800-0000FFFF | 1110xxxx 10xxxxxx 10xxxxxx | 3 |
00010000-001FFFFF | 11110xxx 10xxxxxx 10xxxxxx10xxxxxx | 4 |
00200000-03FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | 5 |
04000000-7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx | 6 |
编码规则为:对于单字节符号,UTF-8编码首位为0,其余各位填充Unicode码即可;对于n字节符号,UTF-8编码首字节前n位全为1,第n+1为0,其余各字节前两位为10,余下的位置填充Unicode码即可。目前,UTF-8编码长度最大为4个字节,所以最多只能表示Unicode编码值的二进制位数为21位的Unicode字符。但是已经能表示当前所有的Unicode字符,因为Unicode的最大码位0x10FFFF也只有21位。5字节和6字节UTF-8属于UCS-4的扩展内容。
UTF-8的特点是:它是一个可变长度的字符编码。对于0x00~0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是6个字节,字符所占用的内存空间大小为1个字节到6个字节不等。从上表可以看出,6字节模板有31个“x”,既可以容纳31位二进制数字。Unicode的最大码位0x7FFFFFFF也只有31位。
例如,“汉”字的Unicode编码是0x6C49.0x6C49在0x0800~0xFFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx.将0x6C49 写成二进制是:0110 1100 0100 1001,用这个比特流依次代替模板中的“x”,得到:11100110 10110001 10001001,即0xE6B189.
(2)UTF-16编码
UTF-16编码以16位无符号整数为单位。Unicode字符的码位需要一个或者两个十六位二进制数来表示。因此UTF-16也是一个可变长度的字符编码。我们把Unicode编码记作U,编码规则如下:
①如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数。
②如果U>=0x10000,我们先计算U=U-0x10000,然后将U写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码二进制形式就是:110110yyyyyyyyyy 110111xxxxxxxxxx.
从Unicode到UTF-16的编码方式如下所示。
十六进制编码范围 | 十进制码范围 | UTF-16表示方法(二进制) | 字节数量 |
0x0000~0xFFFF | 0~65535 | xxxxxxxx xxxxxxxx | 2 |
0x10000~0x10FFFF | 65536~1114111 | 110110yyyyyyyyyy 110111xxxxxxxxxx | 4 |
为什么U可以被写成20个二进制位?Unicode的最大码位是0x10FFFF,减去0x10000后,U的最大值是0xFFFFF,所以肯定可以用20个二进制位表示。
例如,Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000.用前10位依次替代模板中的x,就得到二进制编码:1101100001000011 1101110000110000,即0xD843 0xDC30.