[Python学习日记-21] Python 中的字符编码(上)
[ASCII 码](#ASCII 码)
[GB2312 和 GBK](#GB2312 和 GBK)
简介
在编程之路上,如果你不把编码问题搞清楚,那么它将像幽灵一般纠缠你整个职业生涯,各种灵异事件会接踵而来(页面乱码之类的),挥之不去。只有充分发挥程序员死磕到底的精神你才有可能彻底摆脱编码问题带来的烦恼。
在[Python学习日记-20] 彻底搞懂二进制中我们已经知道了计算机只读得懂二进制,并且学会了二进制转换为十进制的方法,但是据我们所知,计算机显示的并不是全是数字啊,包括我们现在所看到的这篇文章,都是中英文混合的,可是文字应该怎么转换成数字呢?其实计算机执行的就是比较除暴的强制转换。科学家在面临这个问题的时候强行约定了一个表,把文字和数字对应上,这张表就相当于翻译,可以拿着一个数字来对比对应表找到相应的文字,反之亦然。而最初出现的就是 ASCII 码表
ASCII 码
下面这张表就是计算机显示各种文字、符号的基石
ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是现今最通用的单字节编码系统,并等同于国际标准 ISO/IEC 646。由于计算机是美国人发明的,因此,最早只有127个字母被编码到计算机里,也就是大小写英文字母、数字和一些符号,这个编码表被称为 ASCII 编码,比如大写字母 A 的编码是65,小写字母 z 的编码是122。后128个称为扩展 ASCII 码。在 Python 中可以使用 ord() 方法来查询字符的 ASCII 编码
python
print(ord('a'))
print(ord('A'))
print(ord('0'))
print(ord(0))
代码输出如下:
需要注意的是,计算机当中0和'0'并不是一样的东西,前者是 int (整数)类型,而后者是字符类型。那现在我们就知道了上面的字母符号和数字对应的表是早就存在的。那么根据现在有的一些十进制数字,我们就可以转换成成二进制编码串了,编码之间的转换如下
一个空格对应的数字是0 翻译成二进制就是0(注意字符'0'和整数0是不同的)
一个对勾√对应的数字是251 翻译成二进制就是11111011
大家都清楚计算机的数据都是一个包一个包的并不是单独一个个的就像下图那样
那现在就面临一个问题了,我们怎么知道哪里到哪里是表达一个字符呢?这就要说到断句的重要性了
字符编码中的断句
先看个句子:"我一定要当上海贼王!"
这句话你第一时间想到的意思是什么呢?是"我一定要当上,海贼王!"还是"我一定要当,上海贼王!",可以看出由于断句的不同一句话是可以表达两种意思的,而在中文当中我们的断句方式就是使用各种标点符号进行断句,而在计算机中就不能加各种标点符号了,这是因为计算机只能读懂 0 和 1。
那计算机到底是怎么进行断句的呢?从 ASCII 码表当中可以看出,越是靠前的字符对应的二进制编码都是越短的,而后面的字符编码就是更长的,正是由于这些字符串长的长,短的短,写在一起让我们难以分清每一个字符的起止位置,所以聪明的人类就想出了一个解决办法,既然一共就这255个字符,那最长的也不过是八位二进制(11111111),不如我们就把所有的二进制都转换成8位的,不足的用0来替换。
这样一来,刚刚的两个空格一个对勾就写作000000000000000011111011,读取的时候只要每次读8个二进制位就能能知道每个字符的二进制值了,其中每一位0或者1所占的空间单位为 bit (比特),这是计算机中最小的表示单位,而每8个 bit 组成一个字节,这是计算机中最小的存储单位了,计算机中存储单位如下
bit 位,计算机中最小的表示单位
8bit = 1bytes (KiloByte)一字节,最小的存储单位,1bytes 缩写为 1B
1KB = 1024B (MegaByte)兆字节
1MB = 1024KB (GigaByte)吉字节
1GB = 1024MB (TeraByte)太字节
1TB = 1024GB (PetaByte)拍字节
1PB = 1024TB (ExaByte)艾字节
1EB = 1024PB (ZetaByte)泽字节
1ZB = 1024EB (YottaByte)尧字节
1YB = 1024ZB (Brontobyte)珀字节
1BB = 1024YB (NonaByte) 诺字节
1NB = 1024BB (NonaByte) 诺字节;
1DB = 1024NB (DoggaByte)刀字节。
GB2312 和 GBK
随着时代的发展中国人开始使用上了电脑,上面的 ASCII 码是解决了英文字符的转码问题,那我们中文应该怎么解决呢?显然美国人在设计 ASCII 码时并没有考虑到中国人有一天能用上电脑,所以根本没考虑中文的问题。上个世纪80年代,电脑进入中国,把专家们都难倒了,一个 ASSCII 码使用 8bit 只能存256个字符,我常用汉字就几千个,使用 ASCII 码是根本装不下的。既然用不了于是我们另辟蹊径自己设计出了了 GB2312 编码表,一共存了6763个汉字,基本满足了我们日常所需,它就长下面的样子
code +0 +1 +2 +3 +4 +5 +6 +7+8 +9+A+B +C +D +E +F
B4A0 础 储 矗 搐 触 处 揣 川 穿 椽 传 船 喘 串 疮
B4B0 窗 幢 床 闯 创 吹 炊 捶 锤 垂 春 椿 醇 唇 淳 纯
B4C0 蠢 戳 绰 疵 茨 磁 雌 辞 慈 瓷 词 此 刺 赐 次 聪
B4D0 葱 囱 匆 从 丛 凑 粗 醋 簇 促 蹿 篡 窜 摧 崔 催
B4E0 脆 瘁 粹 淬 翠 村 存 寸 磋 撮 搓 措 挫 错 搭 达
B4F0 答 瘩 打 大 呆 歹 傣 戴 带 殆 代 贷 袋 待 逮
code +0 +1 +2 +3 +4 +5 +6 +7+8 +9+A+B +C +D +E +F
B5A0 怠 耽 担 丹 单 郸 掸 胆 旦 氮 但 惮 淡 诞 弹
B5B0 蛋 当 挡 党 荡 档 刀 捣 蹈 倒 岛 祷 导 到 稻 悼
B5C0 道 盗 德 得 的 蹬 灯 登 等 瞪 凳 邓 堤 低 滴 迪
B5D0 敌 笛 狄 涤 翟 嫡 抵 底 地 蒂 第 帝 弟 递 缔 颠
B5E0 掂 滇 碘 点 典 靛 垫 电 佃 甸 店 惦 奠 淀 殿 碉
B5F0 叼 雕 凋 刁 掉 吊 钓 调 跌 爹 碟 蝶 迭 谍 叠
code +0 +1+2 +3+4 +5+6+7+8 +9 +A+B +C +D +E +F
B6A0 丁 盯 叮 钉 顶 鼎 锭 定 订 丢 东 冬 董 懂 动
B6B0 栋 侗 恫 冻 洞 兜 抖 斗 陡 豆 逗 痘 都 督 毒 犊
B6C0 独 读 堵 睹 赌 杜 镀 肚 度 渡 妒 端 短 锻 段 断
B6D0 缎 堆 兑 队 对 墩 吨 蹲 敦 顿 囤 钝 盾 遁 掇 哆
B6E0 多 夺 垛 躲 朵 跺 舵 剁 惰 堕 蛾 峨 鹅 俄 额 讹
......
这个表格比较大,像上面的一块块的文字区域有72个之多,这导致通过一个字节是没办法表示一个汉字的(因为一个字节最多允许256个字符变种,而现在有6千多个之多,只能使用2个字节进行表示,2**16=65535个变种),自从有了 GB2312 我们就可以愉快的在计算机当中写中文了。
常看文章的小伙伴都会发现,文章当中偶尔会出现中英混杂的情况,比如"我在上海,上海 city 不 city 啊!",这种情况就要求 GB2312 必须同时支持中英文,但是还不能是2个字节表示一个英文字母。这是因为人家 ASCII 码只需要用一个字符就能表示,而 GB2312 需要用2个,那每使用 GB2312 存储一个英文字符需要的存储空间就是 ASCII 码的两倍,例如有一个 2MB 大小的英文文档只要一改编码,就立刻变成 4MB,如果有上万份这种类型的文档,足足有一半的空间都是浪费的,这样搞地主家也没有余粮呀。所以中国专家们左思右想,最终想到了一种办法及兼容了ASCII码(即遇到中文用2个字节,遇到英文直接用ASCII的编码)。
它是如何实现的呢?这个关键就在于如何区别连在一起的2个字节是代表2个英文字母,还是一个中文汉字了。最后专家们把目光放到了扩展 ASCII 码上,因为他们发现 ASCII 码后面的极特殊的字符是很少用到的,如果用到了也能使用 GB2312 上的来代替,并且不会造成大量存储空间被浪费的现象,于是决定如果2个字节连在一起,且每个字节的第1位(也就是相当于128的那个2进制位)如果是1,就代表这是个中文,这个首位是128的字节被称为高字节。也就是2个高字节连在一起,必然就是一个中文。
自1980年发布 GB2312 之后,中文一直用着没啥问题,随着个人电脑进入千家万户,有人发现,自己的名字竟然打印不出来,因为有的姓或者是名太生僻了。于是1995年,专家们又升级了 GB2312,加入了更多字符,甚至连藏语、维吾尔语、日语、韩语、蒙古语等等的统统都包含进去了,可想而知当时国家开发 GBK 是想服务整个亚洲的。而这个编码就叫做 GBK,它支持21000多个汉字,一直到现在,我们的 Windows 系统中文版本的编码就是 GBK。
这就是20世纪关于字符编码的一些事了,在[Python学习日记-22] Python 中的字符编码(下)我们将讲述 unicode 和 utf-8 的那些事。