计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为"编码";反之,将存储在计算机中的二进制数解析显示出来,称为"解码",如同密码学中的加密和解密。在解码过程中,如果使用了错误的解码规则,则导致’a’解析成’b’或者乱码。

1 基础知识

1.1 字符集(Charcater Set)与字符编码(Encoding)

字符集(Charcater Set或Charset):是一个系统支持的所有抽象字符的集合,也就是一系列字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。常见的字符集有:ASCII字符集、GB2312字符集(主要用于处理中文汉字)、GBK字符集(主要用于处理中文汉字)、Unicode字符集等。
字符编码(Character Encoding):是一套法则,使用该法则能够对自然语言的字符的一个字符集(如字母表或音节表),与计算机能识别的二进制数字进行配对。即它能在符号集合与数字系统之间建立对应关系,是信息处理的一项基本技术。通常人们用符号集合(一般情况下就是文字)来表达信息,而计算机的信息处理系统则是以二进制的数字来存储和处理信息的。字符编码就是将符号转换为计算机能识别的二进制编码。

1.2 代码点(code point or code position)

对一个字符集中的所有字符进行编号(赋予一个非负整数的序号),每个字符的编号在这个字符集里都是独一无二的。例如,由所有英文字母组成的字符集中有52个字符(小写字母和大写字母各26个),可以将这些字符依次编号为0、1、2、…51。一个字符的编号称为该字符的代码点
值得注意的是,对字符进行编号有很多种方法,只要保证编号的唯一性即可。
在Unicode标准中,代码点采用十六进制书写,并加上前缀U+,例U+0041就是字母A的代码点。

1.3 编码字符集

对一个字符集中的所有字符进行编号,这种编号后的字符集叫做编码字符集(这里的编码仅仅指编号,不同于下文中的编码)。常见的编码字符集有ASCII、Unicode、GBK等。

1.4 字符集、字符编码、代码点与编码字符集之间的关系

字符集:一堆字符的集合。
代码点:对字符集中的字符进行编号,这个编号的号码就是代码点。
编码字符集:对字符集中的字符进行编号后的集合,包括字符和与之对应的编号。
字符编码:通过建立一种转换规则或者说映射关系,将一个编码字符集中的代码点按照某种规则转换成可以在计算机内存储的二进制比特序列。

2 第一阶段:ASCII字符集&编码

在计算机技术发展的早期,所有的数据在存储和运算时都要使用二进制数表示(因为计算机用高电平和低电平分别表示1和0),例如,像a、b、c、d这样的52个字母(包括大写)以及0、1等数字还有一些常用的符号(例如*、#、@等)在计算机中存储时也要使用二进制数来表示,而具体用哪些二进制数字表示哪个符号,当然每个人都可以约定自己的一套(这就叫编码),而大家如果要想互相通信而不造成混乱,那么大家就必须使用相同的编码规则,于是美国有关的标准化组织就出台了ASCII编码(American Standard Code for Information Interchange,美国信息互换标准代码),统一规定了上述常用符号用哪些二进制数来表示。
由于计算机是美国人发明的,并且计算机在设计时采用8个比特(bit)作为一个字节(byte)。因此,最早只有127个字符被编码到计算机里(即用一个字节的后七位)来表示所有的大写和小写字母,数字0 到9、标点符号,以及在美式英语中使用的特殊控制字符。这套编码规则称为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编码只能解决美国人的编码问题,无法将欧洲的文字表示出来,为了表示更多的欧洲常用字符又对ASCII进行了扩展,又有了EASCII,EASCII用8位表示一个字符,使它能多表示128个字符,支持了部分西欧字符。

ASCII字符集:主要包括控制字符(回车键、退格、换行键等);可显示字符(英文大小写字符、阿拉伯数字和西文符号)。
ASCII编码:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符;但是7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。ASCII字符集映射到数字编码规则如下图所示:
在这里插入图片描述
在这里插入图片描述

3 第二阶段:GBXXXX字符集&编码

等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。如果要表示中文,显然一个字节是不够的,至少需要两个字节,而且还不能和ASCII编码冲突,所以,中国制定了GB2312编码,用来把中文编进去。所以,中国制定了GB2312编码,用来把中文编进去。
等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存呢。于是国人就自主研发,把那些127号之后的奇异符号们直接取消掉。规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。
中国人民看到这样很不错,于是就把这种汉字方案叫做 “GB2312″。GB2312 是对 ASCII 的中文扩展。
但是中国的汉字太多了,后来还是不够用,于是干脆不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是 扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。后来少数民族也要用电脑了,于是我们再扩展,又加了几千个新的少数民族的字,GBK 扩成了 GB18030。从此之后,中华民族的文化就可以在计算机时代中传承了。
因为当时各个国家都像中国这样搞出一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码。当时的中国人想让电脑显示汉字,就必须装上一个”汉字系统”,专门用来处理汉字的显示、输入的问题,装错了字符系统,显示就会乱了套。这怎么办?就在这时,一个叫 ISO (国际标谁化组织)的国际组织决定着手解决这个问题。他们采用的方法很简单:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码!他们打算叫它”Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “UNICODE”。

4 第三阶段:Unicode

UNICODE 开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII 里的那些”半角”字符,UNICODE 保持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于”半角”英文符号只需要用到低8位,所以其高 8位永远是0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。
Unicode(又称统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。
Unicode它从0开始,为每个符号指定一个编号,这叫做"码点"(code point)。比如,码点U+0639表示阿拉伯字母Ain,码点U+0041表示英语的大写字母A,码点U+4E25表示汉字严。
目前,Unicode的最新版本是7.0版,一共收入了109449个符号,其中的中日韩文字为74500个。但是,这么多字符也不是一次性定义的,Unicode将所有字符进行分区定义,每个区中有65536(216)个字符,即成为一个平面,目前一共有17个平面,也就是说,整个Unicode字符集的大小为1114112(65536*17)个。
最前面的65536个字符位,称为基本平面(缩写BMP),它的码点范围是从0一直到65535写成16进制就是从U+0000到U+FFFF。所有最常见的字符都放在这个平面,这是Unicode最先定义和公布的一个平面。剩下的字符都放在辅助平面(缩写SMP),码点范围从U+010000一直到U+10FFFF,这也是Unicode的编码规则。
我们说Unicode只是一个字符集,只规定字符的二进制代码编号,没规定字符是如何进行存储,所以这也造成了一些问题。
比如,汉字严的 Unicode 是十六进制数4E25,转换成二进制数足足有15位(100111000100101),也就是说,这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节或者4个字节,甚至更多。也就是说,这个符号至少需要2个字节来表示其它更大的符号。
因为需要至少2个字节来表示更大的符号,这就导致了两个问题,第一个是如何区别该编码是Unicode还是ASCII,计算机怎么知道该字符是2个字节还是3个字节甚至更多。第二个问题是,众所周知,英文字母只需要一个字节来进行编码,但是如果用2个字节3个字节甚至更多字节来表示这就会造成相应倍数的存储空间的增加,造成了存储空间上的极大浪费。
所以最后也出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式来表示Unicode,随着互联网的普及,强烈要求出现一种统一的编码方式。UTF-8 就是在互联网上使用最广的一种 Unicode 的实现方式,同时也存在UTF-16以及UTF-32,下面对其分别展开讲述。

5 UTF-8、UTF-16、UTF-32的介绍

UTF是“Unicode Transformation Format”的缩写,可以翻译成Unicode字符集转换格式,即怎样将Unicode定义的数字转换成程序数据。

5.1 UTF-8

UTF-8是一种变长的编码方法,这个变化是根据 Unicode 编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,字符长度从1个字节到4个字节不等。越是常用的字符,字节越短,最前面的128个字符,只使用1个字节表示,与ASCII码完全相同。
再次强调一下,UTF-8与Unicode的关系是:UTF-8是Unicode的实现方式之一。
UTF-8是一种边长编码方案,使得在存储上节省了许多空间,因此目前也受到了诸多欢迎,接下来,我们再讲一下UTF-8的编码规则。
UTF-8 的编码规则是:
① 对于单字节的符号,字节的第一位设为 0,后面的7位为这个符号的 Unicode 码,因此对于英文字母,UTF-8 编码和 ASCII 码是相同的。
② 对于n字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10,剩下的没有提及的二进制位,全部为这个符号的 Unicode 码 。
下表总结了编码规则,字母x表示可用编码的位。

Unicode编码(十六进制)UTF-8编码(二进制)
000000-00007F0xxxxxxx
000080-0007FF110xxxxx 10xxxxxx
000800-00FFFF1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8的特点是对不同范围的字符使用不同长度的编码。对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
举个例子:比如说一个字符的 Unicode 编码是 130,显然按照 UTF-8 的规则一个字节是表示不了它(因为如果是一个字节的话前面的一位必须是 0),所以需要两个字节(n = 2)。
根据规则,第一个字节的前 2 位都设为 1,第 3(2+1) 位设为 0,则第一个字节为:110X XXXX,后面字节的前两位一律设为 10,后面只剩下一个字节,所以后面的字节为:10XX XXXX。
130对应的二进制为1000 0010,从右往左将二进制数填充到X里面,所以UTF-8 二进制格式为 11000010 100010010 。
再以一个汉字为例:“马”的 Unicode 编号是:0x9A6C,对应第三个范围(2048 - 65535),其格式为:1110XXXX 10XXXXXX 10XXXXXX。十进制编号为39532, 对应的二进制是 1001 1010 0110 1100,将二进制填入进入就为: 11101001 10101001 10101100 。

5.2 UTF-16

UTF-16介于UTF-8和UTF-32之间,它同时结合了定长和变长两种编码方式的特点
它的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。
因为UTF-16要不就是2个字节,要不就是4个字节,那么有个问题就是但我们遇到两个字节的时候,怎么知道它本身是一个字符,还是需要和另外两个字节放在一起解读呢?
在基本平面上有一段空白区间U+D800~U+DFFF,它没有用来定义字符编号,而是用来映射辅助平面的字符,也称为“代理区”。
辅助平面的字符共有220个(为何是220个,待会解释),那么要对应辅助平面的字符就至少需要20个二进制位,UTF-16将这20位拆成两半,前10位映射在U+D800到U+DBFF(空间大小210),称为高位(H),后10位映射在U+DC00到U+DFFF(空间大小210),称为低位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。
所以,当我们遇到两个字节,发现它的码点在U+D800到U+DBFF之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00到U+DFFF之间,这四个字节必须放在一起解读。
Unicode码点转成UTF-16的时候,首先区分这是基本平面字符,还是辅助平面字符。如果是前者,直接将码点转为对应的十六进制形式,长度为两字节,若是后者,则需要进行上面所说的切分操作,具体看接下来讲的UTF-16的转码方式。
UTF-16编码以16位无符号整数为单位。我们把Unicode编码记作U。编码规则如下(百度百科):
• 如果U<0x10000,U的UTF-16编码就是U对应的16位无符号整数(为书写简便,下文将16位无符号整数记作WORD)。
• 如果U≥0x10000,我们先计算U’=U-0x10000,然后将U’写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。
为什么U’可以被写成20个二进制位?Unicode的最大码位是0x10FFFF,减去0x10000后,U’的最大值是0xFFFFF,所以肯定可以用20个二进制位表示,这就是前面提到的辅助字符总共有2^20个的原因。例如:Unicode编码0x20C30,减去0x10000后,得到0x10C30,写成二进制是:0001 0000 1100 0011 0000。用前10位依次替代模板中的y,用后10位依次替代模板中的x,就得到:1101100001000011 1101110000110000,即0xD843 0xDC30。
按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有两个WORD,第一个WORD的高6位是110110,第二个WORD的高6位是110111。可见,第一个WORD的取值范围(二进制)是11011000 00000000到11011011 11111111,即0xD800-0xDBFF。第二个WORD的取值范围(二进制)是11011100 00000000到11011111 11111111,即0xDC00-0xDFFF。
所以第一个WORD和第二个WORD的高6位二进制都是根据上面两个取值范围所得出的。
接下来顺便看看怎么由UTF-16编码推导Unicode编码。
如果一个字符的UTF-16编码的第一个WORD在0xDB80到0xDBFF之间,那么它的Unicode编码在什么范围内?我们知道第二个WORD的取值范围是0xDC00-0xDFFF,所以这个字符的UTF-16编码范围应该是0xDB80 0xDC00到0xDBFF 0xDFFF。我们将这个范围写成二进制:
1101101110000000 11011100 00000000 - 1101101111111111 1101111111111111
按照编码的相反步骤,取出高低WORD的后10位,并拼在一起,得到
1110 0000 0000 0000 0000 - 1111 1111 1111 1111 1111
即0xe0000-0xfffff,按照编码的相反步骤再加上0x10000,得到0xf0000-0x10ffff。这就是UTF-16编码的第一个WORD在0xdb80到0xdbff之间的Unicode编码范围,即平面15和平面16。因为Unicode标准将平面15和平面16都作为专用区,所以0xDB80到0xDBFF之间的保留码位被称作高位专用替代。
至此UTF-16基本讲完。

5.3 UTF-32

UTF-32是最直观的编码方法,每个码点使用四个字节表示,字节内容一一对应码点。比如,码点0就用四个字节的0表示,码点597D就在前面加两个字节的0。
U+0000 = 0x0000 0000
U+597D = 0x0000 597D
UTF-32的优点在于,转换规则简单直观,查找效率高。缺点在于浪费空间,同样内容的英语文本,它会比ASCII编码大四倍。这个缺点很致命,导致实际上没有人使用这种编码方法,HTML 5标准就明文规定,网页不得编码成UTF-32。

6 编码之间的转换

6.1 计算机系统中的编码

在计算机内存中,统一使用Unicode编码,当需要保存到硬盘或者需要传输的时候,就转换为UTF-8编码;用记事本编辑的时候,从文件读取的UTF-8字符被转换为Unicode字符到内存里,编辑完成后,保存的时候再把Unicode转换为UTF-8保存到文件。
浏览网页的时候,服务器会把动态生成的Unicode内容转换为UTF-8再传输到浏览器。
所以你看到很多网页的源码上会有类似的信息,表示该网页正是用的UTF-8编码。
在这里插入图片描述
有且只有Unicode可以直接被内存识别和使用,但存储是可以有多种编码形式的,取决于你指定的编码方式。
解释:比如在记事本中输入字符之后,保存使用ANSI,那么就是默认的gbk保持;如果使用utf-8保存,那么保存方式就是utf-8了。保存的过程,就是将你输入时产生的Unicode码转为你指定的码的“编码”过程。再次用记事本打开时,会自动识别你保存时的编码方式,因而能再次解码成功。引一张经典图:
在这里插入图片描述
如果你在记事本中输入“中国”,保存时选择ascii码,那么会报错或者读取的时候为乱码,因为ascii码没法对中文对应的Unicode进行编码。

6.2 Python2.x与Python3.x中的编解码

其实,python解释器也类似于一个文本编辑器,它也有自己默认的编码方式。python2.x默认ASCII码,python3.x默认的utf-8,可以通过如下方式查询:

import sys
print(sys.getdefaultencoding())

如果我们不想使用默认的解释器编码,就得需要用户在文件开头声明了。

1 Python2.x中

在Python2.x中,有两种字符串类型:str和unicode类型。str存bytes数据,unicode类型存unicode数据。
由下图可以看出,str类型存储的是十六进制字节数据;unicode类型存储的是unicode数据。utf-8编码的中文占3个字节,unicode编码的中文占2个字节。
字节数据常用来存储和传输,unicode数据用来显示明文,那如何转换两种数据类型呢:
在这里插入图片描述
无论是utf-8还是gbk都只是一种编码规则,一种把unicode数据编码成字节数据的规则,所以utf-8编码的字节一定要用utf-8的规则解码,否则就会出现乱码或者报错的情况。
python2.x编码的特色:
在这里插入图片描述
为什么英文拼接成功了,而中文拼接就报错了?
这是因为在python2.x中,python解释器悄悄掩盖掉了 byte 到 unicode 的转换,只要数据全部是 ASCII 的话,所有的转换都是正确的,一旦一个非 ASCII 字符偷偷进入你的程序,那么默认的解码将会失效,从而造成 UnicodeDecodeError 的错误。python2.x编码让程序在处理 ASCII 的时候更加简单。你付出的代价就是在处理非 ASCII 的时候将会失败。

2 Python3.x中

在Python3.x中,也只有两种字符串类型:str和bytes类型。
str类型存unicode数据,bytse类型存bytes数据,与python2.x比只是换了一下名字而已。
在这里插入图片描述
Python 3最重要的新特性大概要算是对文本和二进制数据作了更为清晰的区分,不再会对bytes字节串进行自动解码。文本总是Unicode,由str类型表示,二进制数据则由bytes类型表示。Python 3不会以任意隐式的方式混用str和bytes,正是这使得两者的区分特别清晰。你不能拼接字符串和字节包,也无法在字节包里搜索字符串(反之亦然),也不能将字符串传入参数为字节包的函数(反之亦然)。

注意:

  • 无论python2,还是python3,与明文直接对应的就是unicode数据,打印unicode数据就会显示相应的明文(包括英文和中文)。
  • decode(解码):其它编码–>unicode,需要注明当前编码格式
    encode(编码):Unicode–>其他编码,需要注明生成的编码格式

 
 

本文来自许多优秀博文的总结,如果感觉本文对您有用,欢迎点赞收藏加关注,方便日后翻阅。如若本文存在错误,同样也欢迎各位路过的大佬批评指正!

推荐阅读:
【Python基础】05.Python基本数据类型之数字
【Python基础】04.运算符(超详细)
【Python基础】03.基本概念(表达式、语句等)以及标识符和关键字
【Python基础】02.Python环境搭建以及PyCharm的安装和配置
【Python基础】01.Python简介
  计算机与编程基础知识

参考链接:
[1]:https://www.cnblogs.com/skynet/archive/2011/05/03/2035105.html
[2]:https://blog.csdn.net/qq_36761831/article/details/82291166
[3]:https://blog.csdn.net/csdndn/article/details/79580019
[4]:https://www.cnblogs.com/cthon/p/9297232.html
[5]:https://blog.csdn.net/zhusongziye/article/details/84261211
[6]:https://blog.csdn.net/HU_YEWEN/article/details/99663652?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.compare&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-6.compare
[7]:https://www.cnblogs.com/raphael5200/p/5998818.html
[8]:https://blog.csdn.net/longintchar/article/details/51079340?utm_medium=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase&depth_1-utm_source=distribute.pc_relevant.none-task-blog-BlogCommendFromMachineLearnPai2-1.nonecase
[9]:https://blog.csdn.net/weixin_37740119/article/details/80108142
[10]:https://www.cnblogs.com/OldJack/p/6658779.html

 

未完待续…

Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐