阅读:1774回复:10
科普第6篇——字符编码
本篇内容有一定深度,请自行选择。
相信各位在使用计算机中对字符编码都很头痛,ASCII、GB2312、GBK、UTF-8、Unicode等等,还有繁体中文的BIG5、日语的Shift-JIS等等。 这篇文章里,详细介绍了GB 2312、Unicode(特别是UTF-8),有兴趣的就请继续吧! 一些约定:这篇文章里十六进制数都会在前面加上“0x”,比如0x6F就表示6F的十六进制数;同理,二进制在前面加上“0b”来表示;未特别说明的,不加任何前缀的表示十进制数。关于进制请看科普第3篇。 十六进制和二进制转换表 ASCII 全称美国国家信息交换标准码(American Standard Code for Information Interchange)。使用7位二进制数来表示128个字符,一般用十进制数0~127来表示,比如48(0b0110000)~57(0b0111001)就表示数字0~9,65(0b1000001)~90(0b1011010)表示大写字母A~Z,97(0b1100001)~122(0b1111010)表示小写字母a~z。此外,0(0b0000000)~31(0b0011111)以及127(0b1111111)是控制字符,比如13(0b0001101)就表示换行符。其他的是一些标点符号。 ASCII被接纳为国际标准ISO 646,我国也将其纳入国家标准GB 1988《信息处理交换用的7位编码字符集标准》。但是,计算机存储单位最小是字节,也就是8位二进制数,因此一般用最高位为0,后7位为ASCII,来表示这些字符,也就是0x00~0x7F,称为基本ASCII;然后将最高位是1的用于增加一些ASCII中没有的字符,称为“扩展ASCII”(Extended ASCII)。比如在字符界面下广泛使用的IBM扩展ASCII。后来国际标准组织制定了ISO 8859,其中的编码为ISO-8859-1,又称为LATIN-1,包含了一些常见的拉丁字母,比如带调号的字母(如é、ü等等),现在广泛地被采用拉丁字母的国家使用,如英语国家、法语国家等。 GB 2312 国际标准组织制定了ISO 2022标准《7位字符集的代码扩充技术》,规定了汉语、日语及朝鲜语等文字的扩充方法,ISO 8859就是在这个扩充方法基础上制定的。我国根据ISO 2022制定了GB 2311《信息处理交换用7位编码字符集的扩充方法》,并根据这个扩充方法制定了GB 2312《信息处理交换用汉字编码字符集——基本集》,这就是我们常见的汉字编码了,GB 2312中的编码是GB 2312-80,但我们一般都称为GB 2312。 因为是根据ISO 2022标准制定的,所以GB 2312是兼容基本ASCII码,当一个字节最高位是0时,表示ASCII字符,当最高位是1时,表示汉字字符。因为汉字使用了两个字节,所以要求两个字节的最高位都是1。这样,表示字符的部分每个字节只有7位。 GB 2312以94为基数(因为ASCII中可以显示的字符,即非控制字符共有94个)形成了一个二维平面,共94行、94列,行号称为区号,列号称为位号,每一个字符在这个二维平面中都有唯一的区号和位号,区号在左,位号在右,组成的4位十进制数就叫做区位码。标准中一共有6763个汉字和682个其他字符,总共7445个字符(其中01-09区为符号;16-55区为一级汉字,按拼音排序;56-87区为二级汉字,按部首/笔画排序;10-15区及88-94区没有使用)。如“中”的区位码就是5448,“国”就是2590。 为了与国际标准一致,又分别在每个字符的区号和位号上加上32,再转换为十六进制数,称为“国标码”。如“中”的区号和位号分别是54、48,加上32之后是86、80,转换为十六进制数是56、50,所以“中”的国标码是0x5650;同理,“国”的国标码就是0x397A。 这还没完,因为计算机中通常是中英文字符混排的,所以为了与ASCII区分开来,还需要把国标码转换为机内码(简称“内码”),就是前面说的两个字节最高位都是1的编码了。如“中”,国标码0x5650转换成两个7位的二进制数是1010110(0x56)和1010000(0x50),然后在前面加一位1,即11010110(0xD6)和11010000(0xD0),所以“中”的机内码是0xD6D0。注意到0b10000000=0x80,所以只要在国标码的高低两位上分别加上0x80就行了,比如“国”的国标码0x397A,高两位是0x39,低两位是0x7A,分别加上0x80,得到“国”的机内码就是0xB9FA。 与机内码相对的就是机外码(简称“外码”),就是我们常用的“输入法”了,一个汉字只有唯一的一个内码,但是可以有多个不同的外码,也可以多个汉字有同一个外码。比如“中”,可以有五笔码“khk”,也可以有拼音码“zhong”,而拼音码“zhong”不光对应“中”,还可以是“种”、“终”等等。 GB 2312之后的国标字符集 GB 2312在中国内地广泛使用,在新加坡等使用简体字的国家和地区也比较流行。但是汉字数量毕竟太少,一些生僻字和繁体字都不能输入,甚至还有一些错误,因此国家后来又制定了一些标准。下面按顺序来简单介绍一下: GB 12345《信息交换用汉字编码字符集 辅助集》,这个字符集和GB 2313类似,只不过是繁体字,也有一些补充的字符,但是由于不能满足需求而没有广泛使用。 GB 13000《信息技术 通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》,这是根据国际标准组织的UCS编码(后面会介绍)来制定,包含了20902个汉字,可以满足绝大多数需求。微软根据GB 13000,对GB 2312进行了扩充,制定了GBK,但是GBK的编码方式和GB 13000并不相同,只是收录了GB 13000中的汉字。GB 13000没有被业界采用。 GB 18030《信息技术 中文编码字符集》,现在的版本是GB 18030-2005,是对GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。这是目前最新的中文国标字符集,共收录汉字70244个。这个标准和GB 2313完全兼容,和GBK基本兼容,收录了Unicode(后面会介绍)中的全部CJK统一汉字(C:汉语、J:日语、K:朝鲜语),还收录了少数民族文字,而且有很大的扩充空间(比如GB 18030-2000时才收录了27533个汉字,而GB 18030最多可定义161万个字元)。 虽然这些编码越来越庞大,能满足任何场合的任何需求,但是,毕竟这是地方制定的编码,随着国际化、全球化的进程,毕竟会遇到和其他地方编码不兼容的情况。因此,自然会有人想到制定一个全球统一的字符编码,这就是下面要介绍的Unicode了。 UCS与Unicode 早在1993年,ISO就发布了ISO 10646标准,其中的字符编码就是UCS(Universal Character Set,通用字符集),旨在统一所有语言所有文字的编码标准。UCS使用了31位的字符编码,如果字符编码的前15位相同,后16位不同,则称这些字符是属于同一个平面(Plane)。绝大多数常用字符位于第一个平面(前15位均为0,即后16位是0x0000~0xFFFF),称为BMP(Basic Multilingual Plane)或Plane 0。其中前256个字符(0x0000~0x00FF)和ISO 8859即LATIN-1相同,以保证兼容性。UCS编码字符用U-xxxxxxxx表示(即U-00000000~U-7FFFFFFF),特别地,BMP中的字符用U+xxxx表示(即U+0000~U+FFFF)。在ISO制定UCS的同时,另一个厂商联盟The Unicode Consortium也在制定全球统一的字符编码,称为Unicode。后来双方都意识到世界上不需要两个统一编码,于是开始联合起来制定标准。虽然UCS标准和Unicode标准有一些不同,但是字符集是完全兼容的,现在一般都称为Unicode,但是字符表示方法沿用UCS(就是前面说的U+、U-这些表示方法)。 和汉字GB 2312有机内码、国标码、区位码类似,Unicode也有只是制定了字符的编码,至于这些编码在机器里怎样存储就需要另外制定了。UTF-32、UTF-16、UTF-8、UTF-7就是这样一些编码,这些编码都是使用的Unicode字符集的。目前最常用的是UTF-8。下面就分别来介绍一下: Unicode和UTF-16、UTF-8这些的关系,打个比方: UTF-32 既然使用了31位的字符编码,自然最简单的就是补一位,凑足32位(4字节),这就是UTF-32,或者称为UCS-4。这种编码是在Unicode编码的最前面补一位0。如“中”是U+4E2D,或者U-00004E2D,UTF-32编码就是0x00004E2D。可见UTF-32的编码和UCS字符表示方式是一样的。 UTF-16 但是4个字节太多了,前面说过,常用字符都在BMP中,只需要表示后16位(2字节)就行了。所以出现了UTF-16,同样,只需要把BMP编码的表示方式直接作为UTF-16编码就行了,比如“中”的UTF-16编码就是0x4E2D。当然,UTF-16也可以扩展不只表示BMP的字符,但是这不是我们讨论的重点,感兴趣的自己去查相关文章吧。 UTF-8 UTF-32和UTF-16虽然比较简单,但是都有不足,一是无法兼容ASCII字符,而且会出现整个字节的8位都是0的情况,这可不好,在C语言中字节0表示字符串的结束,使用UTF-32或UTF-16必然会出错,因此,UNIX之父Ken Thompson提出了UTF-8的编码,很好的解决了这些问题,因此被广泛使用。和UTF-32、UTF-16不同,UTF-8的长度并不是固定的,最少1个字节,最多6个字节。而且UTF-8使用了很巧妙的方式解决了前面说到的问题,下面来具体看一下UTF-8的编码方式吧:
下面就来具体说说UTF-8是怎样编码的吧(后面的都是二进制数,x表示Unicode编码的二进制表示方法的置换位): U-00000000~U-0000007F(U+0000~U+007F):0xxxxxxx 如A(U+0041),转换成二进制取后7位是1000001,故UTF-8表示方法是01000001(0x41)。 U-00000080~U-000007FF(U+0080~U+07FF):110xxxxx 10xxxxxx 如ē(U+0113),转换成二进制取后11位是00100 010011,前5位放到第一个字节,后6位放到第二个字节,UTF-8就是11000100 10010011(0xC4 0x93)(用16进制表示UTF-8时通常每个字节都分开写,而不是连在一起) U-00000800~U-0000FFFF(U+0800~U+FFFF):1110xxxx 10xxxxxx 10xxxxxx 如中(U+4E2D),转换成二进制后是0100 111000 101101,将前4位放到第一个字节,中间6位放到第二个字节,后6位放到第3个字节,得到11100100 10111000 10101101(0xE4 0xB8 0xAD) 到这里大家应该能看出一些端倪了:除U+0000~U+007F的字符外,第一个字节中最前面的1的个数表示这个字符占用的字节数,然后紧跟着0,后面的的字节都是10开头。将Unicode编码转换为二进制数之后,将前面多余的0去除,然后分段填入字节的空位中。这就是UTF-8的编码方式,下面把剩下的部分说明以下,就不举例子了。 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 现在也应该能理解为什么第一个字节的范围是0xC0~0xFD了,因为0b11000000=0xC0,0b11111101=0xFD。同样后面的字节的取值范围是0b10000000~0b10111111(0x80~0xBF)。 UTF-7是在电子邮件中常使用的编码,大家平时基本接触不到,因此不再单独介绍了,有兴趣的可以去查相关文章。 这篇文章介绍的东西比较深入,大家平时基本不会有使用这篇文章中的知识的机会,毕竟英文就是英文,中文就是中文,不会因为使用的字符集不同而感到有什么区别,不过这些对于学习编程还是有一定用处的。 今后的文章也会像这样,既有浅显易懂的入门级,也会有更进一步的提高级,大家根据自己的情况选择性地阅读就行了。 ==================== 之前的文章: 科普第1篇——计算机色彩 科普第1篇补遗——CSS颜色 科普第2篇——光盘 科普第3篇——2、8、10、16 科普第4篇——电池 科普第5篇——浏览器 科普番外篇1——虽然没用但了解一下也很有趣的知识 |
|
沙发#
发布于:2010-11-20 18:16
上日网经常会有乱码,后来才知道要改文字编码就能正常显示了,不然都是些乱码
ps:换了遨游三上网果然是”非同凡响“,切换一下模式连论坛登录都自动退出了。。。 |
|
|
2楼#
发布于:2010-11-20 18:27
表示就看的懂三分之一…= -
我大一下预备自学编程的计划也泡汤了……TAT 等比赛完了再来搞这些……囧 |
|
|
3楼#
发布于:2010-11-20 18:29
话说遨游还可以还核心?是不是和chrome一样的核心?
我只知道ASCII码UTF-x (我居然连全称都记不住,shi.t) 据说当初第一次能在电脑中输入中文的时候还引起一阵轰动呢!好像是编那些码很麻烦很难,有人认为是不可能完成的任务,不过还是弄出来了,呵呵! |
|
4楼#
发布于:2010-11-20 20:05
我…我看得头晕…0 0擦汗…果然咱是脑盲啊…
|
|
|
5楼#
发布于:2010-11-20 20:20
我就知道Servlet里面经常要在拦截器里面加上那么一句:
servletRequest.setCharacterEncoding(“gbk”); 不然就显示得全乱码…… |
|
|
6楼#
发布于:2010-11-20 22:20
曾经为了让自己的音乐不在sybiam系统里变成乱码,我费了九牛二虎之力把几千个ID3转成utf8。
|
|
7楼#
发布于:2010-11-20 22:20
最近学c语言表示对8位16进制数感到头大~~~~~~地址太难算了
其他的多少有点了解 |
|
8楼#
发布于:2010-11-20 22:45
这几个编码当时基础课时候我就没有弄明白
|
|
|
9楼#
发布于:2010-11-20 22:54
虽然因为弄家族,现在我对这些编码名有些熟悉,但到底区别在哪还是不知道。
现在算是明白个大概,但也感到真的很晕啊。 |
|
10楼#
发布于:2010-11-20 23:11
Unicode和UTF-16、UTF-8这些的关系,打个比方:
Unicode就像手机号码,这个号码是唯一的。 而UTF-16、UTF-8就像手机号的不同书写方式。 有的人用+86139XXXXXXXX这样的方式,有的人用139XXXXXXXX这样的方式,有的人用139-XXXX-XXXX这样的方式,但不管什么方式,都是同一个手机号。 |
|