目录
常见的字符编码
本篇文章将对常见的字符编码进行介绍,并重点总结gb2312、utf-8和数字证书中常使用的base64编码。
ANSI
ANSI指American National Standards Institute(美国国家标准学会)。
ANSI 编码,又称为"MBCS(Muilti-Bytes Character Set,多字节字符集)"。ANSI编码不是一种具体的编码方式,而是一种指定在某些环境下使用某些编码方式的标准。比如,在中文环境中ANSI的编码标准为GBK,在日语环境中ANSI的编码标准则是Shift_JIS编码。
Windows系统中ANSI是根据当前系统区域(locale)来设置的,要想修改系统默认的“ANSI编码”,我们可以通过修改系统区域来实现(“控制面板” =>“时钟、语言和区域”=>“区域和语言”=>“管理”=>“更改系统区域设置...”)。
不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。一个很大的缺点是,同一个编码值,在不同的编码体系里代表着不同的字。这样就容易造成混乱。导致了unicode码的诞生。其中,每个语言下的ANSI编码,都有一套一对一的编码转换器,Unicode变成所有编码转换的中间介质。所有的编码都有一个转换器可以转换到Unicode,而Unicode也可以转换到其他所有的编码。
ASCII
美国(国家)信息交换标准(代)码,对应的 ISO 标准为 ISO646 标准。
标准 ASCII 码使用 7 个二进位对字符进行编码,最多可以给256个字符(包括字母、数字、标点符号、控制字符及其他符号)分配(或指定)数值。
基本的 ASCII 字符集共有 128 个字符,其中有 95 个可打印字符,包括常用的字母、数字、标点符号等,另外还有 33 个控制字符。下表展示了基本 ASCII 字符集及其编码:
ASCII码图
0~31及127(共33个)是控制字符或通信专用字符(其余为可显示字符),如控制符:LF(换行)、CR(回车)、FF(换页)、DEL(删除)、BS(退格)、BEL(振铃)等;通信专用字符:SOH(文头)、EOT(文尾)、ACK(确认)等;ASCII值为8、9、10和13分别转换为退格、制表、换行和回车字符。它们并没有特定的图形显示,但会依不同的应用程序而对文本显示有不同的影响。
32~126(共95个)是可打印字符,其中32为空格,48~57为0到9十个阿拉伯数字,65~90为26个大写英文字母,97~122为26个小写字母,其余为一些标点符号、运算符号等。
虽然标准 ASCII 码是 7 位编码,但由于计算机基本处理单位为字节( 1byte = 8bit ),所以一般仍以一个字节来存放一个 ASCII 字符。每一个字节中多余出来的一位(最高位)在计算机内部通常保持为 0 (在数据传输时可用作奇偶校验位)。
扩展 ASCII 码
由于标准 ASCII 字符集字符数目有限,在实际应用中往往无法满足要求。为此,国际标准化组织又制定了 ISO2022 标准,它规定了在保持与 ISO646 兼容的前提下将 ASCII 字符集扩充为 8 位代码的统一方法。
ISO 陆续制定了一批适用于不同地区的扩充 ASCII 字符集,每种扩充 ASCII 字符集分别可以扩充 128 个字符,这些扩充字符的编码均为高位为 1 的 8 位代码(即十进制数 128~255 ),称为扩展 ASCII 码。
Python3对ASCII码操作
ord('a') # 获取ASCII码数字
chr(127) # ASCII码转为字符 返回'\x7f',说明backspace不可打印
GB2312
为了满足国内在计算机中使用汉字的需要,中国国家标准总局发布了一系列的汉字字符集国家标准编码,统称为GB码,或国标码。其中最有影响的是于1980年发布的《信息交换用汉字编码字符集 基本集》,标准号为GB 2312-1980,因其使用非常普遍,也常被通称为国标码。GB2312编码通行于我国内地;新加坡等地也采用此编码。几乎所有的中文系统和国际化的软件都支持GB2312。
GB2312 是ANSI编码里的一种,对ANSI编码最初始的ASCII编码进行扩充。
GB2312是一个简体中文字符集,由6763个常用汉字和682个全角的非汉字字符组成。其中汉字根据使用的频率分为两级。一级汉字3755个,二级汉字3008个。
计算机中汉字的处理
计算机进行汉字信息处理时,使用了四种编码:汉字输入码、汉字内码(机内码)、汉字字形码、汉字交换码(国标码)。
- 汉字输入码:输入码所解决的问题是如何使用西文标准键盘把汉字输入到计算机内。主要可以分为三类:数字编码(如区位码)、拼音编码和字编型码(如五笔字形)。
- 汉字内码:在设备和信息处理系统内部存储、处理、传输汉字时真正使用的代码。
- 汉字字形码:表示汉字字形的字模数据,因此也称为字模码。与输入码相对应,是汉字的输出形式,通常以点阵、矢量函数表示。
- 汉字交换码:国标码只是定义了GB2312标准,并没有在机器中真正使用(笔者的理解)。
此外,区位码也是GB2312标准的一部分,定义了该标准,是国标码的另一种表现形式。
区位码
GB2312字符在计算机中存储是以其区位码为基础的,这是由于字符数量比较大,所以GB2312采用了二维矩阵编码法对所有字符进行编码。
首先构造一个94行94列的方阵,对每一行称为一个“区”,每一列称为一个“位”,然后将所有字符依照下表的规律填写到方阵中。其中,汉字的区码和位码分别占一个存储单元(一个字节),每个汉字占两个存储单元。
GB2312 字符编码分布表
分区范围 | 符号类型 |
第01区 | 中文标点、数学符号以及一些特殊字符 |
第02区 | 各种各样的数学序号 |
第03区 | 全角西文字符 |
第04区 | 日文平假名 |
第05区 | 日文片假名 |
第06区 | 希腊字母表 |
第07区 | 俄文字母表 |
第08区 | 中文拼音字母表 |
第09区 | 制表符号 |
第10-15区 | 无字符 |
第16-55区 | 一级汉字(以拼音字母排序) |
第56-87区 | 二级汉字(以部首笔画排序) |
第88-94区 | 无字符 |
这样所有的字符在方阵中都有一个唯一的位置,这个位置可以用区号、位号合成表示,称为字符的区位码。如第一个汉字“啊”出现在第16区的第01位上,其区位码为1601。因为区位码同字符的位置是完全对应的,因此区位码同字符之间也是一一对应的。这样所有的字符都可通过其区位码转换为数字编码信息。
GB2312双字节编码规则
GB2312规定对收录的每个字符采用两个字节表示,第一个字节为“高字节”,对应94个区;第二个字节为“低字节”,对应94个位。GB2312编码(机内码)范围:A1A1-FEFE(对应区位码0101-9494)。其中,汉字编码范围:B0A1-F7FE,汉字编码:第一字节0xB0-0xF7(对应区号:16-87),第二个字节0xA1-0xFE(对应位号:01-94)。
举例来说,“啊”字是GB2312编码中的第一个汉字,它位于16区的01位,所以它的区位码就是1601。
三码转换
区位码:区号和位号分别加上0xA0就是机内码。
国标码:双字节每个字节分别加上0x80就是机内码,相当于把每个字节最高位置为1。
注:后缀H表明是用十六进制形式来表示的。
例如第一个码位(汉字“啊”)的区位码是1601,区号和位号分别转换成十六进制是1001H,转换过程如下:
区码10H转换:0x10+0xA0=0xB0
位码01H转换:0x01+0xA0=0xA1
所以该码位的GB2312编码是B0A1H。
扩展阅读:为什么有机内码,避免与西文冲突
由于区码和位码的取值范围都是在1-94之间,这样的范围同西文的存储表示冲突。
例如,汉字‘珀’在GB2312中的区位码为7174,其两字节表示形式为71,74;而两个西文字符‘G J’的存储码也是71, 74。这种冲突将导致在解释编码时到底表示的是一个汉字还是两个西文字符将无法判断。
为避免同西文的存储发生冲突,GB2312字符在进行存储时,通过将原来的每个字节第8bit设置为1同西文加以区别,如果第8bit为0,则表示西文字符,否则表示GB2312中的字符,这就产生了机内码。
上面是国标码转机内码的方式,实际存储时,直接将区位码转为机内码即可。将区位码的每个字节分别加上A0H(160)的方法转换为存储码,计算机存储规则是此编码的补码,而且是位码在前,区码在后。例如汉字‘啊’的区位码为1601,其存储码为B0A1H。
GBK
GBK全称《汉字内码扩展规范》(GBK即“国标”、“扩展”汉语拼音的第一个字母,英文名称:Chinese Internal Code Specification),中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司、电子工业部科技与质量监督司1995年12月15日联合以技监标函1995 229号文件的形式,将它确定为技术规范指导性文件。这一版的GBK规范为1.0版。
承上启下
GBK 向下与 GB 2312 编码兼容,向上支持 ISO 10646.1国际标准,是前者向后者过渡过程中的一个承上启下的产物。
ISO 10646 是国际标准化组织 ISO 公布的一个编码标准,即 Universal Multilpe-Octet Coded Character Set(简称UCS),大陆译为《通用多八位编码字符集》,台湾译为《广用多八位元编码字元集》,它与 Unicode 组织的 Unicode 编码完全兼容。ISO 10646.1 是该标准的第一部分《体系结构与基本多文种平面》。我国 1993 年以 GB 13000.1 国家标准的形式予以认可(即 GB 13000.1 等同于 ISO 10646.1)。
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从8140至FEFE(剔除xx7F),共23940个码位,共收录了21003个汉字,完全兼容GB2312-80标准,支持国际标准ISO/IEC10646-1和国家标准GB13000-1中的全部中日韩汉字,并包含了BIG5编码中的所有汉字。
GBK编码方案于1995年10月制定, 1995年12月正式发布,中文版的WIN95、WIN98、WINDOWS NT以及WINDOWS 2000、WINDOWS XP、WIN 7等都支持GBK编码方案。
扩展阅读:国家标准汉字编码发展史
由于ASCII编码不支持中文,因此,当中国人用到计算机时,就需要寻求一种编码方式来支持中文。
于是,国人就定义了一套编码规则:当字符小于127位时,与ASCII的字符相同,但当两个大于127的字符连接在一起时,就代表一个汉字,第一个字节称为高字节(从0xA1-0xF7),第二个字节为低字节(从0xA1-0xFE),这样大约可以组合7000多个简体汉字。这个规则叫做GB2312。
但是由于中国汉字很多,有些字无法表示,于是重新定义了规则:不在要求低字节一定是127之后的编码,只要第一个字节是大于127,就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。这种扩展之后的编码方案称之为GBK标,包括了GB2312的所有内容,同时新增了近20000个新的汉字(包括繁体字)和符号。
但是,中国有56个民族,所以,我们再次对编码规则进行了扩展,又加了近几千个少数民族的字符,于是再次扩展后得编码叫做GB18030。
中国的程序员觉得这一系列编码的标准是非常的好,于是统统称他们叫做"DBCS"(Double Byte Charecter Set 双字节字符集)。
参考资料:https://blog.csdn.net/longwen_zhi/article/details/79704687
Big5
在台湾、香港与澳门地区,使用的是繁体中文字符集。而1980年发布的GB2312面向简体中文字符集,并不支持繁体汉字。在这些使用繁体中文字符集的地区,一度出现过很多不同厂商提出的字符集编码,这些编码彼此互不兼容,造成了信息交流的困难。为统一繁体字符集编码,1984年,台湾五大厂商宏碁、神通、佳佳、零壹以及大众一同制定了一种繁体中文编码方案,因其来源被称为五大码,英文写作Big5,后来按英文翻译回汉字后,普遍被称为大五码。
大五码是一种繁体中文汉字字符集,其中繁体汉字13053个,808个标点符号、希腊字母及特殊符号。大五码的编码码表直接针对存储而设计,每个字符统一使用两个字节存储表示。第1字节范围81H-FEH,避开了同ASCII码的冲突,第2字节范围是40H-7EH和A1H-FEH。因为Big5的字符编码范围同GB2312字符的存储码范围存在冲突,所以在同一正文不能对两种字符集的字符同时支持。
Big5编码的分布如表1-5所示,Big5字符主要部分集中在三个段内:标点符号、希腊字母及特殊符号;常用汉字;非常用汉字。其余部分保留给其他厂商支持。
Big5字符编码分布表
编码范围 | 符号类别 |
8140H-A0FEH | 保留(用作造字区) |
A140H-A3BFH | 标点符号、希腊字母及特殊符号 |
A3C0H-A3FEH | 保留(未开放用于造字区) |
A440H-C67EH | 常用汉字(先按笔划,再按部首排序) |
C6A1H-C8FEH | 保留(用作造字区) |
C940H-F9D5H | 非常用汉字(先按笔划,再按部首排序) |
F9D6H-FEFEH | 保留(用作造字区) |
Big5编码推出后,得到了繁体中文软件厂商的广泛支持,在使用繁体汉字的地区迅速普及使用。目前,Big5编码在台湾、香港、澳门及其他海外华人中普遍使用,成为了繁体中文编码的事实标准。在互联网中检索繁体中文网站,所打开的网页中,大多都是通过Big5编码产生的文档。
Unicode
Unicode是一项业界标准,包括字符集、编码方案等。Unicode为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。
Unicode不是一个新的编码规则,而是一套字符集(为每一个字符分配一个唯一的 ID,学名为码位/码点/Code Point),可以将Unicode理解为一本世界编码的字典。Unicode存储在计算机内存里肯定是需要编码的,那么就有UTF-8,UTF-16,UTF-32等编码方案。
目前的Unicode字符分为17组编排,0x0000 至 0x10FFFF,每组称为平面(Plane),而每平面拥有65536个码位,共1114112个。
在表示一个Unicode的字符时,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。
Unicode与GBK编码的转换
比如汉字“路”,在gbk中的编码为“\xc2\xb7”,对应的二进制为:“1100 0010 1011 0111”。由于gbk的2个字节的高字节是为了区分中文和ASCII,所以将“1100 0010 1011 0111”高字节的“1”去掉后,就对应Unicode字符集中的0100 0010 0011 0111”
同时,“路”在Unicode字符集中的位置是“\u8def”(python中的Unicode类型),因此可以通过“\u8def”在Unicode字符集中找到“路”对应的编码为“4237”,对应的二进制为:“0100 0010 0011 0111”。
UTF-8
Unicode的事实标准
Unicode比较浪费网络带宽和硬盘,为解决此问题,在Unicode的基础上,定义了一套编码规则(将“码位”转换为字节序列的规则),这个新的编码规则就是UTF-8,采用1-4个字符进行传输和存储数据。其中,Unicode码位转换为utf-8序列称为“编码”,utf-8序列转换为Unicode码位称为“解码”。
utf-8编码规则
使用下面的模板进行Unicode到utf-8的转换。
1、对于单个字节的字符(7bits,标准ASCII码范围),第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。这意味着 ASCII 码那个年代的文档用 UTF-8 编码打开完全没有问题。
2、对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。
Unicode十六进制码点范围 | UTF-8 二进制 |
0000 0000 - 0000 007F | 0xxxxxxx |
0000 0080 - 0000 07FF | 110xxxxx 10xxxxxx |
0000 0800 - 0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
0001 0000 - 0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
反之,从utf-8到unicode编码规则如下。
utf-8区分每个字符的开始是根据字符的高位字节来区分的,比如用一个字节表示的字符,第一个字节高位以“0”开头;用两个字节表示的字符,第一个字节的高位为以“110”开头,后面一个字节以“10开头”;用三个字节表示的字符,第一个字节以“1110”开头,后面俩字节以“10”开头;用四个字节表示的字符,第一个字节以“11110”开头,后面的三个字节以“10”开头。
举例
比如汉字“智”,utf-8编码是“\xe6\x99\xba”对应的二进制为:“11100110 10011001 10111010”,由于utf-8中一个汉字是3个字节,所以对应的模板为“0000 0800-0000 FFFF -> 1110xxxx 10xxxxxx 10xxxxxx”。
| 第1个字节 | 第2个字节 | 第3个字节 |
Utf-8编码 | 1110 0110 | 1001 1001 | 1011 1010 |
对应模板 | 1110 xxxx | 10xx xxxx | 10xx xxxx |
得到模板中的“x” |
|
|
|
Unicode编码 | 0110 0110 0111 1010 (对应十六进制 667A) | ||
Unicode码位 | 667A |
同样,根据Unicode中字符的编码位置,也能找到对应的utf-8编码。
参考资料:https://blog.csdn.net/hezh1994/article/details/78899683
UTF-8和Unicode与GBK的关系
utf-8--------decode(解码)----->>Unicode类型<<-------decode(解码)-----gbk
utf-8<<--------encode(编码)----->>Unicode类型<<-------encode(编码)----->>gbk
参考资料:https://blog.csdn.net/longwen_zhi/article/details/79704687
Base64
由于ASCII 码称为了国际标准,所以我们要把其它字符转成 ASCII 就要用到 base64。
- utf-8 -> base64(编码) -> ASCII
- ASCII -> base64(解码) -> utf-8
或者,
- 在一端发送GB2312编码->根据Base64规则->转换成ASCII码
- 接收端收到ASCII码->根据Base64规则->还原到GB2312编码。
这样就可以让只支持 ASCII 的计算机支持 utf-8 和gb2312了。
有的电子邮件系统(比如国外信箱)不支持非英文字母(比如汉字)传输,为了能让邮件系统正常的收发信件,就需要把由其他编码存储的符号转换成ASCII码来传输。
数字证书中的Base64编码
在X.509公钥证书、电子邮件中,都经常用到Base64编码,那么为什么要作一下这样的编码呢?
和电子邮件一样,计算机中数据都是按ascii码存储的,而ascii码的128~255之间的值是不可见字符。而在网络上交换数据时,比如说从A地传到B地,往往要经过多个路由设备,由于不同的设备对字符的处理方式有一些不同,这样那些不可见字符就有可能被处理错误,这是不利于传输的。所以就先把数据先做一个Base64编码,统统变成可见字符,这样出错的可能性就大降低了。因此,对证书来说,特别是根证书,一般都是作Base64编码的,因为它要在网上被许多人下载。电子邮件的附件一般也作Base64编码的,因为一个附件数据往往是有不可见字符的。
Base64编码规则
Base64的原理很简单,首先,准备一个包含64个字符的数组,作为索引表:
['A', 'B', 'C', ... 'a', 'b', 'c', ... '0', '1', ... '+', '/']
然后,对将要转换的二进制数据进行处理,每3个字节分为一组,一共是3x8=24bit,再将每24个bits划为4个小组,每个小组正好6个bit。
这样每组(6 bits)转为一个数字,就得到4个数字,假设为n1、n2、n3、n4,将每个数字作为索引,然后查表,获得4个字符,就是编码后的字符串。
分析
- 6bits范围0-63,正好对应索引表中的64个字符。
- Base64编码会把3字节的二进制数据编码为4字节的文本数据,长度增加33%,好处是编码后的文本数据可以在邮件正文、网页等直接显示。
如果要编码的二进制数据不是3的倍数,最后会剩下1个或2个字节怎么办?Base64用\x00字节在末尾补足后,再在编码的末尾加上1个或2个=号,表示补了多少字节,解码的时候,会自动去掉。如下。
# Python内置的base64可以直接进行base64的编解码
>>> import base64
>>> base64.b64encode('binary\x00string')
'YmluYXJ5AHN0cmluZw=='
>>> base64.b64decode('YmluYXJ5AHN0cmluZw==')
'binary\x00string'
由于标准的Base64编码后可能出现字符+和/,在URL中就不能直接作为参数,所以又有一种"url safe"的base64编码,其实就是把字符+和/分别变成-和_。如下。
>>> base64.b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd++//'
>>> base64.urlsafe_b64encode('i\xb7\x1d\xfb\xef\xff')
'abcd--__'
>>> base64.urlsafe_b64decode('abcd--__')
'i\xb7\x1d\xfb\xef\xff'
还可以自己定义64个字符的排列顺序,这样就可以自定义Base64编码,不过,通常情况下完全没有必要。
Base64是一种通过查表的编码方法,不能用于加密,即使使用自定义的编码表也不行。
Base64适用于小段内容的编码,比如数字证书签名、Cookie的内容等。
由于=字符也可能出现在Base64编码中,但=用在URL、Cookie里面会造成歧义,所以,很多Base64编码后会把=去掉:
'abcd' -> 'YWJjZA==' # 标准Base64:
'abcd' -> 'YWJjZA' # 自动去掉=:
去掉=后怎么解码呢?因为Base64是把3个字节变为4个字节,所以,Base64编码的长度永远是4的倍数,因此,需要加上=把Base64字符串的长度变为4的倍数,就可以正常解码了。
一个能处理去掉=的base64解码函数:
>>> base64.b64decode('YWJjZA==')
'abcd'
>>> base64.b64decode('YWJjZA')
Traceback (most recent call last):
...
TypeError: Incorrect padding
>>> safe_b64decode('YWJjZA')
'abcd'
其他base64编码典型使用场景
Base64是一种任意二进制到文本字符串的编码方法,常用于在URL、Cookie、网页中传输少量二进制数据。
1、XML传输
一个xml当中包含另一个xml数据,此时如果将xml数据直接写入显然不合适,将xml进行适当编码存入较为方便,事实上xml当中的字符一般都是可见字符(0-127之间),但是由于中文的存在,可能存在不可见字符,直接将字符打印在外层xml的数据中显然不合理,那么怎么办呢?可以使用base64进行编码,然后存入xml,解码反之。其实还有个办法,将byte的值写在xml当中,空格或者,分开,这样也可以将byte数据传入,不过这样更浪费空间,并且不易保存。
2、协议规定
有些文本协议不支持不可见字符的传递,只能用大于32的可见字符来传递信息(协议规定)
机器与编码
一般地说,开放的操作系统(LINUX 、WINDOWS等)采用ASCII 编码,而大型主机系统(MVS 、OS/390等)采用EBCDIC 编码。
在发送数据给对方前,需要事先告知对方自己所使用的编码,或者通过转码,使不同编码方案的两个系统可沟通自如。
Python 中的编码
python3中str默认为Unicode的编码格式。
Unicode是一32位编码格式,不适合用来传输和存储,所以必须转换成utf-8,gbk等等。
所以在Python3中必须将str类型转换成bytes类型的。
注:
- Python中str类型转bytes类型,相当与Unicode转gbk、utf-8等类型。
- b'代表字符编码格式为bytes。
- \x表示十六进制的字符型变量。
- utf-8默认24位占3个8位16进制数。
- gbk中国编码默认占16位2个8位16进制数字。
在Python中使用encode的方式可以进行字符的编码。
>>>a = "啊"
>>> a.encode("utf-8")
b'\xe5\x95\x8a'
>>> a.encode("gb2312")
b'\xb0\xa1'
URLEncode
Base64解决了传输字符编码支持和可见性问题。
URLEncode解决了HTTP协议www表单传递字符串的规范性问题。当字符串数据以url的形式传递给web服务器时,字符串中是不允许出现空格和特殊字符串的。
Urlencode函数的说明:
urlencode ( string $str ) : string
此函数便于将字符串编码并将其用于 URL 的请求部分,同时它还便于将变量传递给下一页。
参数
str 要编码的字符串
返回值
返回字符串,此字符串中除了-_.之外的所有 非字母数字 字符都被替换成百分号(%)后跟 两位十六进制数
例子
www.baidu.com?a=nihao
上面的例子可以看出 a 的值是nihao
如果要把 a 赋值为“=”字符呢?这样:www.baidu.com?a== ,肯定不行,因为“=” 是特殊字符,所以把 “=” UrlEncode后是 “%3d”。
www.baidu.com?a=%3d
服务器拿到 a 解码得到 “=”,所以说 url 是限制性编码。