ASCII、GB2312、GBK、Unicode、UCS-2、UCS-4、UTF-8总结

一直以来常被编码带来的乱码问题所困惑,花了点时间粗略搞懂了这些编码之间的来龙去脉。
主要参考的是这三篇文章:
123

ASCII表

Only:口口口口 口口口口
每个“口”代表1bit,只存放0/1
最大为二进制1111 1111,即最多只表示到十进制255

思想:8bit=1Byte表示1个字符,因此最多只能表示2^8=256个字符,一开始美国的计算机设计者只使用了126个字符,后来计算机发展到全球时,256-126=130个字符装不完中文、韩文等字符。


ASCII编码表

GB2312

Case1:口口口口 口口口口
Case2:口口口口 口口口口 口口口口 口口口口

以下采用16进制表示{0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F},如:十进制15表示为16进制F(即0xF),十进制16表示为16进制10(即0x10)。

思想:这是中国人自己设计的汉字字符集和编码,是扩展ASCII表之后的映射字典,总共收录了简化汉字 6763 个,字母和符号 682个。采用2Byte或者1Byte表示一个字符,2Byte表示的字符俗称“全角字符”;1Byte表示的是半角字符。

编码方法:规定,一个小于127的字符的意义与ASCII的意义相同,但两个大于等于127的字符连在一起时,就表示一个汉字!

所以他的编码顺序会出现这样的情况:从“十六进制B0FE”(十进制45310)直接跳到“十六进制B1A1(十进制45473)”,也就是十进制的45311~45472中间这162个号码没有用到哦。

其中,高位字节用16进制的{A1,A2,…A9,AA,AB,AC,AD,AE,AF,B0,B1,…BF,…,F0…F5,F6,F7}表示;低位字节用16进制的{A1,…FE}表示。
GB2312
十六进制的B0A1(B0:高位;A1:低位)= 十进制45217,因此“啊”在GB2312中是第45217号;“阿”的编号是45218号。因此GB2312是对ASCII的扩展,里面包含了126个ASCII编码。

GB2312编码表

GBK

由于中国汉字太多,GB2312编码方式还是装不完,后来人们规定,把GB2312中的规定改掉,只要求高位字节(Byte)大于等于十进制127就表示汉字的开始,里面包含了GB2312的所有编码,并增加了近20000个汉字。

比GB2312多处了一些码位
对应与上面的GB2312来说,从“十六进制B0FE”(十进制45310)直接跳到“十六进制B140(十进制45376)”,也就是十进制的45311~45375中间只有64个号码没有用到。

因此GBK是GB2312的扩展,后来加上了少数名族等字符,也就是拓展为了GB18030编码。

GBK编码表

Unicode

早期 Only:(口口口口 口口口口)+(口口口口 口口口口)

这是包括了地球上所有文化、所有字母和符号的编码,它包含了ASCII编码里的所有内容,并规定所有的字符都必须用两个字节Byte表示(不像GBK或者GB2312那样,有的用两个字节表示,有的只用一个字节表示),其中,ASCII表中涉及到的那126个字符,其数字ID与ASCII的数字ID保持一致(即十六进制44在Unicode和ASCII中都表示的是字符“D”),但他完全没有考虑到所有国家原有的编码系统,加上各国的编码方案和范围完全不一样,所以GBK和Unicode之间没有一个可以相互转换编码的公式,只能通过查表来进行转换。

Unicode编码顺序
在GBK中,中文字符“啊”对应的是十六进制B0A1,即十进制第45217号,而在Unicode中,“啊”对应的是十六进制554A,即十进制第21835号。同时可以看到,GBK中字符“啊”后面接的是字符“阿”;而在Unicode中,“啊”的下一个字符是“啋”(Unicode和GBK之间的编码顺序完全不一致)。

所以Unicode基本包含了世界上所有国家的字符,但是它和ANSI编码的编码方式(顺序及编号等)又完全不一样。

ANSI编码:2个Byte来表示1个字符的编码。如:简体中文Windows操作系统中,ANSI 编码代表 GBK 编码;繁体中文Windows操作系统中,ANSI编码代表Big5;日文Windows操作系统中,ANSI 编码代表 Shift_JIS 编码。

汉字的Unicode编码范围

UTF-8 、UTF-16、UTF-32、UCS-2与UCS-4

Unicode定义了所有可以用来表示字符的数值集合,如十进制21835(二进制101 0101 0100 1011)映射为中文字符“啊”。但是在保存文件和传输过程中,计算机只认识0、1。为了传输方便,计算机每次传输8个bit或者16个bit,而不会传输3bit或者1bit,也就是传输方式使用的都是2的次方数量。

所以你可以发明一种编码方案,如把15个bit补齐为16个bit,简单的在它的二进制前多加一个0,即0101 0101 0100 1011去存储这个字符“啊”,这也就是最早的UCS-2编码,使用固定长度的2Byte去表示一个字符。可是后来还是发现2Byte所能表示的容量还是过小了,因此就拓展到了使用4Byte表示世界上的符号,总共2^32=42亿的数字ID呢!可是当人们将16个bit的USC-2拓展为USC-4,此时,“啊”的编码就变成了0000 0000 0000 0000 0101 0101 0100 1011,这种固定长度简单的编码方式变得更加占用空间了,多了太多太多没有必要的0。

UTF-16则是属于UCS-2的拓展版本,对于UCS-2中的编码使用2Byte表示,对于拓展的UCS-2无法容纳的字符,则用4Byte对其进行编码。此时,UTF-8这种可变长的编码方式才应运而生。

Unicode在网络上的传输,有大概三个标准: UTF-8、UTF-16和UTF-32,即分别每次传输 8个bit、16个bit和32个bit,而UTF-16和UTF-32基本无人使用。

举个栗子:想要储存“I am 中国人”
GBK储存:“I am ”包含5个半角字符(两个空格),他们都的储存空间是5Byte=40bit;“中国人”包含3个全角字符,储存空间是6Byte=48bit。因此,GBK储存这句话需要用到5+6=11Byte。
UCS-2储存:需要用到8个二字节的空间,也就是16Byte。

可以看到英文越多,使用固定宽度的字节去储存字符浪费的空间越大,但是世界上流行最广的是英语,加上在很久以前,无论是硬盘大小还是网络速度,都非常的有限,人们非常希望对传输的内容进行压缩和限制

UTF-8就是对Unicode再进行一次映射,而且他希望的是,最常用的英文字母,使用最少的字节,不常用的,反正也不常用,多两个字节其实没关系。

最短的 UTF-8 字符只需要使用 1 个字节,最长是 5 个字节。

UTF-8 的编码:
1) 对于单字节的符号,字节的第一位设为0,后面7位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
2) 对于n字节的符号(n > 1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的Unicode码。

	Unicode符号范围      |        UTF-8编码方式
	(十六进制)        	|       (二进制)
	----------------------+---------------------------------------------
	1字节储存编码范围 | 0xxxxxxx(只占用8bit)
	2字节储存编码范围 | 110xxxxx 10xxxxxx(16bit)
	3字节储存编码范围| 1110xxxx 10xxxxxx 10xxxxxx(24bit)
	4字节储存编码范围| 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx(32bit)

UTF-8 的解码:
如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

如:“你”的 Unicode 是16进制4F60(二进制100 1111 0110 0000),4F60为3字节储存编码范围,因此UTF-8编码为:11100100 10111101 10100000,UTF-8的16进制表示则为e4bda0。

win笔记本的一个Bug:新建一个文本文件时,记事本的编码默认是ANSI, 如果你在ANSI的编码输入汉字,那么他实际就是GB系列的编码方式,在这种编码下,输入“联通”两个字,之后再打开笔记本,你会发现一堆乱码。

查询上述GBK的表可以看到,“联”对应的十六进制数字为C1AA(二进制1100 0001+1010 1010);“通”十六进制为CDA8(二进制1100 1101+1010 1000);第一二个字节、第三四个字节的起始部分的都是“110”和“10”,正好与UTF8规则里的两字节模板是一致的,于是再次打开记事本时,记事本就误认为这是一个UTF8编码的文件,把第一个字节的110和第二个字节的10去掉,我们就得到了“000 0110 1010”,再补上前导的0,即0000 0000 0110 1010,即Unicode的16进制006A,即字母“j”,而“通”字在经过UTF-8解码后得到Unicode的16进制为0368,这个数字没有字符映射,因此这二字无法正常显示。

由此引发了一个比较常见的问题就是:我已经把文件保存成了 XX 编码,为什么每次打开,还是原来的 YY 编码?!原因就在于此,你虽然保存成了 XX 编码,但是系统识别的时候,却误识别为了 YY 编码,所以还是显示为 YY 编码。为了避免这个问题,微软公司弄出了一个叫 BOM 头的东西。

可以看到直接以Unicode的原始形式的数字进行固定字节的编码储存是一种极大的浪费,而且也不利于互联网的传输。因此常说的UTF-8编码准确的来说只是Unicode数字ID二进制表示的一种压缩形式。

文件带BOM头的问题

当使用类似 WINDOWS 自带的记事本等软件,在保存一个以UTF-8编码的文件时,会在文件开始的地方插入三个不可见的字符(如:UTF-8的文档开始插入的0ddxEF 0xBB 0xBF)。它是一串隐藏的字符,用于让记事本等编辑器识别这个文件是否以UTF-8编码。如字符“严”的Unicode16进制数字为4E25,使用不同格式储存这个字符时,不同格式之间的BOM头如下所示:

1)ANSI:文件的编码就是两个字 节D1 CF,这正是严的 GB2312 编码,这也暗示 GB2312 是采用大头方式存储的,没有BOM头。
2)UTF-16小端:编码是四个字节FF FE 25 4E,其中FF FE表明是小头方式存储,真正的编码是4E25。
3)UTF-16大端:编码是四个字节FE FF 4E 25,其中FE FF表明是大头方式存储。
3) UTF-8:编码是六个字节EF BB BF E4 B8 A5,前三个字节EF BB BF表示这是UTF-8编码。

大端小端的问题主要是针对UTF-16这种编码方式来说的,因为有的存储器是按大端模式,有的是小端模式,也就是说,在大端模式里,“严”的16进制是高位在前,低位在后,即4E25;小端模式则是低位在前,高位在后,即254E;可是当4E25与254E在编码中都有对应的字符时,如何判断到底是哪个字符便成了问题,此时使用BOM也是解决这个方案的方法之一。
0xEF 0xBB 0xBF
windows对于utf-8编码的文件自带BOM,但是其他系统utf-8编码默认不带BOM,使用python读取这类带BOM头文件的时候常常会出现一些问题。如:12。因此把文件保存为UTF-8的时候,不要带有 BOM 头则可以避免这种问题。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值