编码方式,字符集,字符编码
1. 相关概念
1.1 位(bit)
位是计算机存储数据的最小单位
,1或者0就表示1位,如10010010就表示8位的二进制数。
1.2 字节(byte)
字节是计算机信息技术用于计量存储容量的一种计量单位,作为一个单位来处理的一个二进制数字串,是构成信息的一个小单位。
1 |
|
1.3 字符(Character)
字符是指计算机中使用的字母、数字、字和符号,是数据结构中最小的数据存取单位。如a、A、B、b、大、+、*、%
等都表示一个字符。
1.4 字符集与编码字符集(Character set)
字符集
是指各种文字和符号的集合,包括各个国家文字、标点符号、图形符号、数字等。编码字符集
是所有字符以及对应代码值的集合。编码字符集中的每个字符都对应一个唯一的代码值。这些代码值就称为码点值
(码值,code point),可以看做字符在编码字符集中的编号。- 字符与码点值的对应关系是通过编码字符集规定好了的。
- 像
ASCII字符集
中的字符a
,对应的码值就是97
。
其实我们并不用对字符集
与编码字符集
做太大区分,编码字符集
在概念上只是比字符集
多了一个与码值
的对应关系,在后面的讲解中我们提到的字符集
都是指的编码字符集
。
常见的字符集有 ASCII字符集
、GBK字符集
、GBxxxx字符集
、Unicode字符集
等。
1.5 编码与解码
- 编码:把字符按照指定字符集编码成字节。
- 解码:把字节按照指定字符集解码成字符。
字符在计算机中的存储与读取:
- 存储:字符 –> 码值 –> 二进制 –> 存储
- 读取:二进制 –> 码值 –> 字符 –> 显示
1.6 字符编码(Character encoding)
编码字符集中只规定了字符的代码值并未规定具体如何存储,字符编码方式解决了字符在计算机中如何存储的问题。
是将编码字符集中的字符代码值转换为实际的存储字节序列的一种映射规则。
常见的字符编码方式有 ASCII字符编码
、GBK字符编码
、UTF-32字符编码
、UTF-8字符编码
等。
1.7 编码字符集与编码方式间对应关系
字符集和字符编码一般都是成对出现的,每种编码字符集至少对应一种字符编码方式,也可以对应多种编码方式。
常见字符集与字符编码的对应关系:
2.字符集的来历
计算机的目的是为了高效处理数据。但发明计算机时,必然面临一个问题:如何把计算的字符存放到计算机当中去。因为如果想让计算机处理信息,最起码要把自己的字符存到计算机中。那美国人要存放哪些字符呢?其实就是英文字母(大小写)
、数字
、标点符号
、特殊字符
。
但能够直接把这些字符存放到计算机底层吗?肯定是不行的。因为计算机底层是硬件,只能存储0和1二进制数据。为了让计算机能存储这些字符,于是对他们所用到的所有字符进行了编号
,从0开始编,一直编到了127,总共128个字符。示例如下:
1 |
|
这些编号
我们又称为码点
,我们将这套从0~127编码的字符集称为标准ASCII字符集
。
现在把字符转化成了数字,那怎么把数字转化成 0,1 呢,没错,就是直接把码点转成二进制形式。
从0~127只需要用7
位二进制数即可。我们知道,计算机底层最少是存储一个字节的,因此码点用了8
位二进制数来表示,第8位(最高位)统一设置为 0。
3. 常见字符集及字符编码
3.1 ASCII 字符集
ASCII(American Standard Code for Information Interchange, 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII则可以部分支持其他西欧语言,并等同于国际标准ISO/IEC 646。
字符集范围:ASCII一共定义了128个字符,包括33个控制字符,和95个可显示字符。大部分的控制字符已经被废弃。
编码方式:ASCII码为单字节,用7位二进制数表示,由于计算机1个字节是8位二进制数,所以最高位为0,即00000000-01111111或0x00-0x7F。Unicode,GBXXX,UTF-8等字符编码都兼容ASCII编码。
3.1.1 扩展 ASCII
由于标准ASCII字符集字符有限,往往无法满足实际需求,因此国际标准组织制定了在与标准ASCII规范相兼容的前提下将ASCII字符集扩充为8位代码的方法。
每种扩充ASCII字符集可以扩充128个字符,这些扩充字符的编码均为最高位为1的8位代码。扩充的ASCII字符集即为扩展ASCII字符集,编码方式称为扩展ASCII编码。
编码方式:常见的一种扩展ASCII为ISO-8859-1(也称为Latin-1)编码规范,用于支持部分欧洲语言。
3.2 GB2312 字符集
但是随着计算机的普及,我们中国人也开始使用字符集。但对于我们中国人而言,采用 ASCII 进行字符存储肯定是不够的,所以出现了GB 系列。
GB 2312 或 GB 2312–80 是中华人民共和国国家标准简体中文字符集(Chinese Internal Code Specification),全称《信息交换用汉字编码字符集·基本集》,又称GB0,由中国国家标准总局发布,1981年5月1日实施。GB 2312编码通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
字符集范围:GB 2312标准共收录6763个汉字,其中一级汉字3755个,二级汉字3008个;同时收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的682个字符。
GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。但对于人名、古汉语等方面出现的罕用字和繁体字,GB 2312不能处理,但后来 GBK 及 GB 18030 汉字字符集的出现解决了这些问题。
分区
GB 2312中对所收汉字进行了“分区”处理,每区含有94个汉字/符号。这种表示方式也称为区位码。
01–09区为特殊符号。
16–55区为一级汉字,按拼音排序。
56–87区为二级汉字,按部首/笔画排序。
举例来说,“啊”字是GB 2312之中的第一个汉字,它的区位码就是1601。
10–15区及88–94区则未有编码。
编码格式
在使用GB 2312的程序通常采用EUC储存方法,以便兼容于ASCII。
每个汉字及符号以两个字节来表示。第一个字节称为“高位字节”,第二个字节称为“低位字节”。
“高位字节”使用了0xA1–0xF7(把01–87区的区号加上0xA0),“低位字节”使用了0xA1–0xFE(把01–94加上0xA0)。 由于一级汉字从16区起始,汉字区的“高位字节”的范围是0xB0–0xF7,“低位字节”的范围是0xA1–0xFE,占用的码位是72*94=6768。其中有5个空位是D7FA–D7FE。
3.3 GBK 字符集
GBK的K为汉语拼音 Kuo Zhan(扩展)中“扩”字的声母。
GBK,汉字内码扩展规范,全名为《汉字内码扩展规范(GBK)》1.0版,英文全称Chinese Internal Code Extension Specification。由中华人民共和国全国信息技术标准化技术委员会1995年12月1日制订,国家技术监督局标准化司和电子工业部科技与质量监督司1995年12月15日联合以《技术标函[1995]229号》文件的形式公布。
字符集范围:GBK对GB 2312-80进行扩展, 总计拥有 23940 个码位,共收入21886个汉字和图形符号,其中汉字(包括部首和构件)21003 个,图形符号 883 个。
编码格式:GBK 亦采用双字节表示,总体编码范围为8140-FEFE,首字节在81-FE 之间,尾字节在40-FE 之间,剔除 xx7F一条线。
GBK向下完全兼容GB2312-80编码。支持GB2312-80编码不支持的部分中文姓,中文繁体,日文假名,还包括希腊字母以及俄语字母等字母。不过这种编码不支持韩国字,也是其在实际使用中与unicode编码相比欠缺的部分。
3.4 GB18030 字符集
GB18030,全称:国家标准GB 18030-2005《信息技术 中文编码字符集》,是中华人民共和国现时最新的内码字集,是GB 18030-2000《信息技术 信息交换用汉字编码字符集 基本集的扩充》的修订版。与GB 2312-1980完全兼容,与GBK基本兼容;支持GB 13000(93版等同于Unicode 1.1;2010版等同于Unicode 4.0)及Unicode的全部统一汉字,共收录汉字70,244个。
本规格的初版是由中华人民共和国信息产业部电子工业标准化研究所起草,由国家质量技术监督局于2000年3月17日发布。
此标准内的单字节编码部分、双字节编码部分,和四字节编码部分收录的中日韩统一表意文字扩展A区汉字,为强制性标准。其他部分则属于规模性标准。在中华人民共和国境内所有软件产品,都需要支持这个同时包含单字节、双字节和四字节编码的规格。
GB 18030主要有以下特点:
- 和UTF-8一样都采用多字节编码,每个字可以由1个、2个或4个字节组成。
- 编码空间庞大,最多可定义161万个字元。
- 支持中国国内少数民族的文字,不需要动用造字区。
- 汉字收录范围包含繁体汉字以及日韩汉字。
编码方式
- 单字节,其值从0x00到0x7F。
- 双字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x40到0xFE(不包括0x7F)。
- 四字节,第一个字节的值从0x81到0xFE,第二个字节的值从0x30到0x39,第三个字节从0x81到0xFE,第四个字节从0x30到0x39。
3.5 Unicode字符集
我们中国可以针对自己国家的字符设置字符集并规定编码方式,那其他国家也是可以针对自己国家的字符进行字符集设置与规定编码方式。那这样,就有了很多种的字符集编码:巴基斯坦码、韩文码、迪拜码等等。
有了这么多种字符集编码,当计算机在世界普及与信息互传的时候,就会带来很多的问题:比如使用韩文码进行编码,然后发给了迪拜,迪拜使用了迪拜码进行解码,就肯定会出现乱码
的问题。
这时候就需要一个统一的字符集与编码方式,每个国家都遵循这个字符集与编码规范,信息就可以在世界互传了。
Unicode字符集是统一码联盟为了统一所有语言的文字和符号而制定的编码字符集。编码方式包括:UTF-8
、UTF-16
和UTF-32
。
Unicode(中文:万国码、国际码、统一码、单一码)是计算机科学领域里的一项业界标准。它对世界上大部分的文字系统进行了整理、编码,使得电脑可以用更为简单的方式来呈现和处理文字。Unicode伴随着通用字符集的标准而发展,同时也以书本的形式对外发表。Unicode至今仍在不断增修,每个新版本都加入更多新的字符。
Unicode也是一种字符编码方法,它由国际组织设计,可以容纳全世界所有语言文字的编码方案。
Unicode的学名是”Universal Multiple-Octet Coded Character Set”,简称为UCS。UCS可以看作是”Unicode Character Set”的缩写。
Unicode备受认可,并广泛地应用于电脑软件的国际化与本地化过程。有很多新科技,如可扩展置标语言、Java编程语言以及现代的操作系统,都采用Unicode编码。
但是UCS只是规定如何编码,并没有规定如何传输、保存这个编码。例如“汉”字的UCS编码是6C49(虽然我只占用两个字节),我可以用4个ascii数字来传输、保存这个编码;也可以用utf-8编码,3个连续的字节E6 B1 89来表示它。关键在于通信双方都要认可。UTF-8、UTF-16、UTF-32都是被广泛接受的方案。
编码方式
Unicode的编码方式与ISO 10646的通用字符集概念相对应。目前实际应用的Unicode字符集规范为UCS-2,使用16位的编码空间。也就是每个字符占用2个字节。这样理论上一共最多可以表示2的16次方(即65536)个字符。基本满足各种语言的使用。实际上当前版本的统一码并未完全使用这16位编码,而是保留了大量空间以作为特殊使用或将来扩展。而且 unicode 对汉字支持不怎么好,ucs2才六万多个,所以Unicode只能排除一些几乎不用的汉字,为了表示所有汉字,unicode也有用ucs4规范,就是用四个字节来编码字符,不过现在普遍采用还是ucs2
实现方式
Unicode原编码占用两个字节,在使用ASCII字符时,高位字节的8位始终为0,这会造成空间的浪费。为了避免这种浪费,Unicode的实现方式不同于编码方式。一个字符的Unicode编码是确定的。但是在实际传输过程中,由于不同系统平台的设计不一定一致,以及出于节省空间的目的,对Unicode编码的实现方式有所不同。
Unicode的实现方式称为Unicode转换格式(Unicode Transformation Format,简称为UTF)。 UTF-8、UTF-16、UTF-32都是将数字转换到程序数据的编码方案。
3.5.1 UTF-16编码
UTF-16是变长编码方式,每个字符编码为2或4字节,是Unicode最早的编码方式。
在Java语言里,它使用的是Unicode字符集和UTF-16编码。也就是说Java能表示出全部Unicode字符集中规定了的字符,然后在内存中存储时,通过UTF-16中定义的规则将其转换成字节。
例如“a”这个字母,在Unicode中规定要用十六进制下的0x61
来表示,但是实际存储的时候可不是直接存的0x61,而是查表,发现应该是0x0061
。所以,Java中一个char类型在内存中占用两个字节,因为他们存储的是UTF-16编码后的字节,而UTF-16则是把所有Unicode字符都使用固定了两字节的方式进行编码。
3.5.2 UTF-32编码
四个字节表示一个字符,确实做到了有容乃大,可以包含全世界所有的字符。但是这套编码方案并不被全世界所接纳,因为大家认为这种编码方式方式太过于奢侈
,一个字符要用四个字符来表示太占存储空间了,通讯效率会变低。
- 比如使用
ASCII编码
方式,原来一个字符
只需要一个字节
即可。而使用Unicode字符集下的UTF-32编码
方式,是需要用四个字节
的,这就意味着需要白白浪费三个字节
,则相当于需要的存储空间是相当于ASCII编码用到的存储空间的三倍
! - 比如使用
GBK编码
方式,原来一个中文字符
只需要两个字节
。而使用UTF-32编码
方式就会浪费多两个字节
,这样存储空间相较于GBK的会多出一倍
。
因此,Unicode字符集虽然是很好的思想,但这种UTF-32编码方案并没有被世界接纳,在很多业务场景下我们也不会使用到这套编码方案。
3.5.3 UTF-8 编码
由于UTF-32编码方案的弊端太过突出,于是国际组织又为Unicode字符集设计了一套改变字符苍生和世界的编码方案 —— UTF-8编码方案
UTF-16比起UTF-8,好处在于大部分字符都以固定长度的字节(2字节)储存,但UTF-16却无法兼容于ASCII编码。
UTF-8(8-bit Unicode Transformation Format)是一种针对Unicode的可变长度字符编码,也是一种前缀码。其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部分修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。
UTF-8使用一至六个字节为每个字符编码(尽管如此,2003年11月UTF-8被RFC 3629重新规范,只能使用原来Unicode定义的区域,U+0000到U+10FFFF,也就是说最多四个字节):
- 一个字节编码(Unicode范围由U+0000至U+007F):128个 US-ASCII 字符。
- 两个字节编码(Unicode范围由U+0080至U+07FF):带有附加符号的拉丁文、希腊文、西里尔字母、亚美尼亚语、希伯来文、阿拉伯文、叙利亚文及它拿字母。
- 三个字节编码(Unicode范围由U+0800至U+FFFF):其他基本多文种平面(BMP)中的字符(这包含了大部分常用字,如大部分的汉字)。
- 其他极少使用的Unicode 辅助平面的字符使用四至六字节编码(Unicode范围由U+10000至U+1FFFFF使用四字节,Unicode范围由U+200000至U+3FFFFFF使用五字节,Unicode范围由U+4000000至U+7FFFFFFF使用六字节)。
对上述提及的第四种字符而言,UTF-8使用四至六个字节来编码似乎太耗费资源了。但UTF-8对所有常用的字符都可以用三个字节表示,而且它的另一种选择,UTF-16编码,对前述的第四种字符同样需要四个字节来编码,所以要决定UTF-8或UTF-16哪种编码比较有效率,还要视所使用的字符的分布范围而定。
UTF-8编码方式(二进制):
1 |
|
计算机通过码点值的区间范围
判断出应使用UTF-8编码方式的多少字节
进行存储,比如知道字符a
的码点值为97
,因此计算机知道了应使用一个字节存储
。又比如知道了字符 我
的码点值为 25105
,在三个字节存储对应的码点值区间范围,所以计算机会使用三个字节进行对字符 我
的存储。所以,使用UTF-8编码对字符a我m
的底层存储方式为:
1 |
|
编码的问题解决了,那计算机解码的时候又是如何识别的呢?其实很简单,计算机在解析二进制数据时,看前几位(bit)的数值:
- 发现第一位为0,则说明计算机在编码的时候是将一个字符用用一个字节进行存储,因此只需要解析一个字节即可。比如将
01100001
先转成十进制的码点值为97
,再根据 Unicode字符集得到该码值对应的字符为a
。 - 发现第一位不为0
- 如果前几位是
110
,说明计算机在编码的时候是将一个字符用用两个字节进行存储,因此需要整体解析两个字节,舍去前缀一个110
和一个10
,其它位从左至右拼接组成二进制数。 - 如果前几位是
1110
,说明计算机在编码的时候是将一个字符用用三个字节进行存储,因此需要整体解析三个字节,舍去前缀一个1110
和两个10
,其它位从左至右拼接组成二进制数。比如对于字符我
的底层存储编码11100110 10001000 10010001
,去掉前缀一个1110
和两个10
后,由其它位组成二进制数0110 001000 010001
,将该二进制转成十进制码点值为25105
,再根据Unicode字符集得到该码值对应的字符为我
,就可以将该字符显示到计算机屏幕了。 - 如果前几位是
11110
,说明计算机在编码的时候是将一个字符用用四个字节进行存储,因此需要整体解析四个字节,舍去前缀一个11110
和三个10
,其它位从左至右拼接组成二进制数。
- 如果前几位是
python的bytes
bytes是一种比特流(字节流),它的存在形式是01010001110这种。我们无论是在写代码,还是阅读文章的过程中,肯定不会有人直接阅读这种比特流,它必须有一个编码方式,(如utf-8,gbk等)使得它变成有意义的比特流,而不是一堆晦涩难懂的01组合。因为编码方式的不同,对这个比特流的解读也会不同,最终显示的信息也不同。
Python有个内置函数bytes()可以将字符串str类型转换成bytes类型,bytes类型实际上是一串01的组合,但为了在ide环境中让我们相对直观的观察,它被表现成了 b'\\xe4\\xb8\\xad\\xe6\\x96\\x87'
这种形式,开头的b表示这是一个bytes类型。\\xe4
是十六进制的表示方式,它占用1个字节的长度,因此”中文“被编码成utf-8后,我们可以数得出一共用了6个字节,每个汉字占用3个,这印证了上面的论述。在使用内置函数bytes()的时候,必须明确encoding的参数,不可省略。
Python字符串类str里有一个encode()方法,它是从字符串向比特流的编码过程。而bytes类型恰好有个decode()方法,它是从比特流向字符串解码的过程。而且,我们查看Python源码会发现bytes和str拥有几乎一模一样的方法列表,最大的区别就是encode和decode。
从实质上来说,字符串在磁盘上的保存形式都是是01的组合,即编码存储,解码展示。
如果,上面的阐述还不能让你搞清楚两者的区别,那么记住下面两几句话:
- 在将字符串存入磁盘和从磁盘读取字符串的过程中,Python自动地帮你完成了编码和解码的工作,你不需要关心它的过程。
- 使用bytes类型,实质上是告诉Python,不需要它帮你自动地完成编码和解码的工作,而是用户自己手动进行,并指定编码格式。但是ASCII数据会自动解码
Python已经严格区分了bytes和str两种数据类型,你不能在需要bytes类型参数的时候使用str参数,反之亦然。这点在读写磁盘文件时容易碰到。
在bytes和str的互相转换过程中,实际就是编码解码的过程,必须显式地指定编码格式。
1 |
|
我们再把字符串s1,转换成gbk编码的bytes类型:
1 |
|
在pycharm中
1 |
|
大家知道unicode的存储效率低,会浪费很多空间,因此在保存文本时,很多时候并不是用unicode编码方式,而是转化为其他的编码编码方式如utf-8,gbk,还有日文,韩文编码等,下面以读取一个用utf-8的文本为例:
首先将 utf-8 编码的文件解码成 unicode 编码方式,读取到记事本中,修改之后,再编码成 utf8 的方式进行保存。总的来说,就是计算机内存中是以unicode编码为桥梁的。
如果说从从其他编码方式转换成unicode这一过程出错,就会产生乱码,例如文本使用日文编码保存的,你用gbk来解码就会产生乱码。
1 |
|
字符串在Python内部的表示是unicode编码,因此,在做编码转换时,通常需要以unicode作为中间编码,即:先将其他编码的字符串解码(decode)成unicode,再从unicode编码(encode)成另一种编码。
decode的作用是将其他编码的字符串转换成unicode编码。
如str1.decode(‘gb2312’),表示将gb2312编码的字符串str1转换成unicode编码,最后类型为str。
encode的作用是将unicode编码转换成其他编码的字符串。
如str2.encode(‘gb2312’),表示将unicode编码的字符串str2转换成gb2312编码,最后类型为bytes。
因此,转码的时候一定要先搞明白,字符串str是什么编码,然后decode成unicode,然后再encode成其他编码,如果一个字符串已经是unicode了,再进行解码则将出错,因此通常要对其编码方式是否为unicode进行判断:
1 |
|
Reference
https://www.cnblogs.com/mlgjb/p/7899534.html
https://www.cnblogs.com/chiguozi/p/5860364.html
http://www.ituring.com.cn/book/miniarticle/61192
https://betheme.net/xiaochengxu/27326.html?