qakcn
学生会会长
学生会会长
  • 注册日期2008-10-31
  • 最后登录2021-01-05
  • 生日1988-8-18
  • 光玉3394颗
阅读:1623回复:10

科普第6篇——字符编码

楼主#
更多 发布于:2010-11-20 17:39
本篇内容有一定深度,请自行选择。

相信各位在使用计算机中对字符编码都很头痛,ASCII、GB2312、GBK、UTF-8、Unicode等等,还有繁体中文的BIG5、日语的Shift-JIS等等。
这篇文章里,详细介绍了GB 2312、Unicode(特别是UTF-8),有兴趣的就请继续吧!

一些约定:这篇文章里十六进制数都会在前面加上“0x”,比如0x6F就表示6F的十六进制数;同理,二进制在前面加上“0b”来表示;未特别说明的,不加任何前缀的表示十进制数。关于进制请看科普第3篇
十六进制和二进制转换表
在科普第3篇里简单讲了一下十六进制和二进制的转换,就是每4位二进制数对应一位十六进制数。不足4位(倍数)的在前面补0。这里给出全部转换表(前面十六进制,后面二进制)。
0——0000    1——0001    2——0010    3——0011    4——0100    5——0101    6——0110    7——0111
8——1000    9——1001    A——1010    B——1011    C——1100    D——1101    E——1110    F——1111
如二进制数1010110101,共10位,在前面补0,补足4的倍数12位,再每四位一组 0010 1011 0101,然后根据上面的对应转换成十六进制数,就是2B5。
第一行转换表将二进制数去掉最高位的0(保留后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这些的关系,打个比方:
Unicode就像手机号码,这个号码是唯一的。
而UTF-16、UTF-8就像手机号的不同书写方式。
有的人用+86139XXXXXXXX这样的方式,有的人用139XXXXXXXX这样的方式,有的人用139-XXXX-XXXX这样的方式,但不管什么方式,都是同一个手机号。


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的编码方式吧:
  • 对于U+0000~U+007F,即前128个字符,只占用1个字节,即0x00~0x7F,这样就坐到了和ASCII兼容。

  • 大于U+007F的字符用2~6个字节表示,每个字节的最高位都是1,区别于ASCII最高位为0的情况,同时也避免了字节0的情况(因为至少有一位是1)。

  • 在大于U+007F的字符中,第一个字节的取值在0xC0~0xFD之间,后面的字节取值范围都是0x801~0xBF(下面会具体说说为什么)。

  • 所有Unicode字符都可以用UTF-8编码来表示。

  • UTF-8编码最长6个字节,BMP字符的编码最长3个字节。

  • 不光是字节0(0x00),0xFE和0xFF也不会出现在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——虽然没用但了解一下也很有趣的知识
喜欢0 评分0
movepsp
光坂学士生
光坂学士生
  • 注册日期2009-04-26
  • 最后登录2024-05-03
  • 生日1987-12-28
  • 光玉3775颗
沙发#
发布于:2010-11-20 18:16
上日网经常会有乱码,后来才知道要改文字编码就能正常显示了,不然都是些乱码

ps:换了遨游三上网果然是”非同凡响“,切换一下模式连论坛登录都自动退出了。。。
FF13-2在哪里?
回复(0) 喜欢(0)     评分
凌涵汐雨
光坂基金会
光坂基金会
  • 注册日期2010-06-15
  • 最后登录2021-09-07
  • 生日1950-1-1
  • 光玉3595颗
2楼#
发布于:2010-11-20 18:27
表示就看的懂三分之一…= -
我大一下预备自学编程的计划也泡汤了……TAT
等比赛完了再来搞这些……囧
回复(0) 喜欢(0)     评分
琥珀
光坂硕士生
光坂硕士生
  • 注册日期2009-06-07
  • 最后登录2020-12-09
  • 生日1989-12-26
  • 光玉6903颗
3楼#
发布于:2010-11-20 18:29
话说遨游还可以还核心?是不是和chrome一样的核心?

我只知道ASCII码UTF-x   (我居然连全称都记不住,shi.t)

据说当初第一次能在电脑中输入中文的时候还引起一阵轰动呢!好像是编那些码很麻烦很难,有人认为是不可能完成的任务,不过还是弄出来了,呵呵!
回复(0) 喜欢(0)     评分
serffyme水草~
光坂学士生
光坂学士生
  • 注册日期2008-12-11
  • 最后登录2020-04-28
  • 生日1989-11-27
  • 光玉4876颗
4楼#
发布于:2010-11-20 20:05
我…我看得头晕…0 0擦汗…果然咱是脑盲啊…
回复(0) 喜欢(0)     评分
紫木出梦
光坂基金会
光坂基金会
  • 注册日期2008-11-16
  • 最后登录2014-07-04
  • 生日1988-11-3
  • 光玉911颗
5楼#
发布于:2010-11-20 20:20
我就知道Servlet里面经常要在拦截器里面加上那么一句:
 servletRequest.setCharacterEncoding(“gbk”);
不然就显示得全乱码……    
春心莫与花争发,一寸相思一寸灰。
回复(0) 喜欢(0)     评分
初音
光坂基金会
光坂基金会
  • 注册日期2010-05-29
  • 最后登录2015-10-10
  • 生日1990-10-3
  • 光玉722颗
6楼#
发布于:2010-11-20 22:20
曾经为了让自己的音乐不在sybiam系统里变成乱码,我费了九牛二虎之力把几千个ID3转成utf8。
回复(0) 喜欢(0)     评分
ct一剑
光坂博士生
光坂博士生
  • 注册日期2010-10-04
  • 最后登录2014-08-21
  • 生日1991-10-16
  • 光玉13939颗
7楼#
发布于:2010-11-20 22:20
最近学c语言表示对8位16进制数感到头大~~~~~~地址太难算了
其他的多少有点了解
回复(0) 喜欢(0)     评分
桜舞雪咒
学生会会长
学生会会长
  • 注册日期2009-05-01
  • 最后登录2021-10-30
  • 生日1991-3-23
  • 光玉11781颗
8楼#
发布于:2010-11-20 22:45
这几个编码当时基础课时候我就没有弄明白
回复(0) 喜欢(0)     评分
梦幻的夏
光坂学士生
光坂学士生
  • 注册日期2008-10-29
  • 最后登录2014-08-21
  • 生日1986-4-15
  • 光玉3005颗
9楼#
发布于:2010-11-20 22:54
虽然因为弄家族,现在我对这些编码名有些熟悉,但到底区别在哪还是不知道。
现在算是明白个大概,但也感到真的很晕啊。
回复(0) 喜欢(0)     评分
qakcn
学生会会长
学生会会长
  • 注册日期2008-10-31
  • 最后登录2021-01-05
  • 生日1988-8-18
  • 光玉3394颗
10楼#
发布于:2010-11-20 23:11
Unicode和UTF-16、UTF-8这些的关系,打个比方:
Unicode就像手机号码,这个号码是唯一的。
而UTF-16、UTF-8就像手机号的不同书写方式。
有的人用+86139XXXXXXXX这样的方式,有的人用139XXXXXXXX这样的方式,有的人用139-XXXX-XXXX这样的方式,但不管什么方式,都是同一个手机号。
回复(0) 喜欢(0)     评分
游客

返回顶部