文章目录
- 字符(character)
- 字符集(character set)
- 字符编码(character encoding)
- 编码类型
- 字符的存储
- ASCII(American Standard Code for Information Interchange)
- GB2312
- GBK
- UNICODE
- UTF-8
在python中,字符编码问题困扰着很多初学者,那么我们应该如何理解字符编码,做到心中“有谱”,能正确的处理字符呢?本文即是从这个需求出发,来帮助理解字符编码。按照惯例,先介绍相关的概念,再通过实例帮助理解。
字符(character)
字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。
字符集(character set)
字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等
字符编码(character encoding)
- 计算机要准确的“识别”并处理各种字符集文字,就需要按照相应的规则,对各种文字进行字符编码
- 字符编码(encoding)和字符集不同。字符集只是字符的集合,不一定都适合进行网络传送以及某些处理,有时须经编码(encode)后才能应用。
- 字符编码就是以二进制的数字映射字符集的字符。
因此,对字符进行编码,是信息交流的技术基础。
编码类型
SBCS(single-Byte Chactacter System(Set)):单字节字符系统(集),表示在这种编码类型下,所有字符只用一个字节表示。
MBCS(Multi-Byte Chactacter System(Set)):多字节字符系统(集),表示在这种编码类型下,所有字符可以由一个或多个字节表示,实际几个字节要看使用的具体编码方案。
SBCS/MBCS 是编码的一种类型,而不是某个特定编码的名称。
字符的存储
我们知道计算机只能"认识" 0和1,那么计算机中存储介质(如,内存或硬盘)对信息的存储方式就是1和0。物理上,硬盘的盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方代表数字1,凹的地方代表数字0。
任何文字要存储到计算机中,都需要先编码成相应的二进制数,然后再存储。双方进行数据通讯时,也要保证发送方和接收方的编码方式相同,否则也是鸡同鸭讲,就会出现我们遇到的乱码问题。
ASCII(American Standard Code for Information Interchange)
美国信息交换标准码,是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。等同于国际标准ISO/IEC 646,到目前为止共定义了128个字符。
字符集:从符号(NUL)到符号(DEL)
字符编码范围:二进制:00000000——01111111; 十进制:0-127
占用字节:1字节(8bit); 盘片储存方式:00000000 ——11111111
GB2312
GB2312 是对 ASCII 的中文扩展,兼容ASCII,采用双字节编码。中文名称为:信息交换用汉字编码字符集
GB2312编码适用于汉字处理、汉字通信等系统之间的信息交换,通行于中国大陆;新加坡等地也采用此编码。中国大陆几乎所有的中文系统和国际化的软件都支持GB 2312。
基本集共收入汉字6763个和非汉字图形字符682个。整个字符集分成94个区,每区有94个位。每个区位上只有一个字符,因此可用所在的区和位来对汉字进行编码,称为区位码 (在线查询)。
区位码是一个四位的十进制数,它的前两位叫做区码,后两位叫做位码。
分区范围 | 符号类型 |
第01区 | 中文标点、数学符号以及一些特殊字符 |
第02区 | 各种各样的数学序号 |
第03区 | 全角西文字符 |
第04区 | 日文平假名 |
第05区 | 日文片假名 |
第06区 | 希腊字母表 |
第07区 | 俄文字母表 |
第08区 | 中文拼音字母表 |
第09区 | 制表符号 |
第10-15区 | 无字符 |
第16-55区 | 一级汉字(以拼音字母排序) |
第56-87区 | 二级汉字(以部首笔画排序) |
第88-94区 | 无字符 |
每个国标码或区位码都对应着一个唯一的汉字或符号。国标码是一个四位十六进制数,把换算成十六进制的区位码加上A0A0H,就得到国标码。一般我们看到的16进制显示的字符编码值就是国标码。区位码表
国标码和区位码的对应关系:国标码 = 区位码(16进制)+0xA0A0
下面举两个实际的例子,来说明国标码和区位码的具体转换,弄着这个原理后,以后就可以自己进行查表和计算,真正的"由表及里"。
通过查表得到:
“啊”字 的区位码为1601(十进制)
区码和位码分别加上0xA0,就得到GB2312编码:1601 --> 0x1001–>0x1001+0xA0A0–>0xB0A1(gb2312编码查询)
s = '啊'
bytes_gb2312 = bytes(s, encoding='gb2312')
print(bytes_gb2312) # 输出结果为 b'\xb0\xa1'
“汉” 字 的区位码为 2626(十进制)
GB2312 编码值计算:2626 --> 0x1A1A --> 0x1A1A + 0xA0A0 -->BABA
s = '汉'
bytes_gb2312 = bytes(s, encoding='gb2312')
print(bytes_gb2312) # 输出结果为 b'\xba\xba'
GBK
GBK 兼容ASCLL 兼容 GB2312, 是GB2312的扩展。
中文全称:汉字内码扩展规范
英文名称:Chinese Internal Code Specification
GBK编码,是在GB2312-80标准基础上的内码扩展规范,使用了双字节编码方案,其编码范围从0x8140至0xFEFE(剔除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编码方案。
UNICODE
Unicode(统一码、万国码、单一码)是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码。目的就是为了将全世界所有文字都收录到一个统一的字符集,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式公布。
Unicode为世界上所有字符都分配了一个唯一的数字编号,分为17组编排,这个编号范围从 0x000000 到 0x10FFFF(21位),每组称为平面(plane),每个plane有65536个码位,共有65536*17=1114112个,每个字符都有一个唯一的Unicode编号,这个编号一般写成16进制,在前面加上U+。例如:“马”的Unicode是U+9A6C。Unicode 只是一个字符集,它只规定了字符的编号,却没有规定这个编号应该如何在计算机中存储。那要怎么存储呢,这样就产生了所谓的UTF-8,UTF-16,UTF-32等。下面只具体介绍UTF-8,其他两种编码类似,搞懂一个其他也就能懂。
UTF-8
一种以字节为单位的可变长度字符编码,UTF-8使用1-4字节为每个unicode字符编码,理论上可以最多到6个字节长。这个变化是根据 Unicode 编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多。
编码规则:
Unicode编码(十六进制) | UTF-8 字节流(二进制) |
000000-00007F | 0xxxxxxx |
000080-0007FF | 110xxxxx 10xxxxxx |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
对于0x00-0x7F之间的字符,UTF-8编码与ASCII编码完全相同。UTF-8编码的最大长度是4个字节。从上表可以看出,4字节模板有21个bit(x),即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
为了更直观的理解,举个栗子:在线验证
“汉”字的Unicode编码是0x6C49。按照UTF-8编码规则,0x6C49在0x0800-0xFFFF之间,使用3字节模板: 1110xxxx 10xxxxxx 10xxxxxx。
将0x6C49写成二进制是:0110 1100 0100 1001, 用这个比特流依次代替模板中的x,得到: 11100110 10110001 10001001,即E6 B1 89。那么这三个字节即是存储在计算机中的“汉”字的编码。
“马”的Unicode编号是:0x9A6C, 按照UTF-8编码规则,在0x0800-0xFFFF之间,其格式为: 1110XXXX 10XXXXXX 10XXXXXX,
0x9A6C对应的二进制是 1001 1010 0110 1100,将二进制填入进入就为: 11101001 10101001 10101100,即E9 A9 AC。
s = '汉'
print("{}".format(s)) # 输出结果为 汉
print("{!r}".format(s)) # 输出结果为 '汉'
print("{!a}".format(s)) # 输出结果为 '\u6c49'
bytes1 = bytes(s, encoding='utf-8')
print(bytes1) # 输出结果为 b'\xe6\xb1\x89'
for b in bytes1:
print("{:#b}".format(b))
# for 循环的输出结果为:
# 0b11100110
# 0b10110001
# 0b10001001
# 代码执行的结果和前面分析计算的结果完全相同。
小结:
通常,“字符"被收录进"字符集”,然后按照"字符集"的规模(即要表示多少文字),设计"编码方案",“编码方案"的 目的是为了使计算机能够表示、识别"字符集"中的"字符”。“字符”、"字符集"是便于人进行阅读的;“编码"是便于计算机进行"阅读”、存储。
在字符编码的发展进程中,不同的国家和地区都有 对自己本地的文字收录进"字符集",这样就产生了各种"字符集"。汉字相关的字符集就有,GB2312、GBK等。
平常我们所说的“字符集”,如果"字符集"就只有一种"编码",比如:ASCII、GB2312, GBK等,除了有“字符的集合”这层含义外,同时也包含了“编码”的含义。但是Unicode字符集有多种编码方式,如UTF-8、UTF-16等,但一般我们说unicode编码,通常是值UTF-8。
对于"字符" — “字符集” — “字符编码” 三者之间关系
"字符"和"字符集" 通常是便于人 表达的 编号 方式
"字符编码" 通常是 让计算机 表达的 编号方式
对于GB2312
**字符 | 区位码(字符集中的编号) | 字符 --- 区位码(字符集中的编号) --- 国标码(字符编码的编号,也是在计算机中的存储表示) |
“汉” | 0x1A1A | 0xBABA |
对于unicode—utf8
字符 | unicode编码(字符集中的编号) | utf8字节流(字符编码的编号,也是在计算机中的存储表示) |
“汉” | 0x6c49 | 0xe6b189 |
结语:
通过本文对字符编码的理解,我相信,只要搞懂了"字符" — “字符集” — “字符编码” 三者之间关系,不管遇到什么字符集,都能正确的处理不同字符集的字符的转换问题。因为这是最本质的原理,编程语言只是用来 表达字符的工具而已。