python之字符编码原理


在python中,字符编码问题困扰着很多初学者,那么我们应该如何理解字符编码,做到心中“有谱”,能正确的处理字符呢?本文即是从这个需求出发,来帮助理解字符编码。按照惯例,先介绍相关的概念,再通过实例帮助理解。

字符(character)

​ 字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等。

字符集(character set)

​ 字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等

字符编码(character encoding)

  1. 计算机要准确的“识别”并处理各种字符集文字,就需要按照相应的规则,对各种文字进行字符编码

  2. 字符编码(encoding)和字符集不同。字符集只是字符的集合,不一定都适合进行网络传送以及某些处理,有时须经编码(encode)后才能应用。

  3. 字符编码就是以二进制的数字映射字符集的字符。

因此,对字符进行编码,是信息交流的技术基础。

编码类型

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-00007F0xxxxxxx
000080-0007FF110xxxxx 10xxxxxx
000800-00FFFF1110xxxx 10xxxxxx 10xxxxxx
010000-10FFFF11110xxx 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

**字符区位码(字符集中的编号)字符 --- 区位码(字符集中的编号) --- 国标码(字符编码的编号,也是在计算机中的存储表示)
“汉”0x1A1A0xBABA

对于unicode—utf8

字符unicode编码(字符集中的编号)utf8字节流(字符编码的编号,也是在计算机中的存储表示)
“汉”0x6c490xe6b189

结语:

通过本文对字符编码的理解,我相信,只要搞懂了"字符" — “字符集” — “字符编码” 三者之间关系,不管遇到什么字符集,都能正确的处理不同字符集的字符的转换问题。因为这是最本质的原理,编程语言只是用来 表达字符的工具而已。

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

sif_666

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值