细说python字符编码(二)

本文介绍python编码基础要点

背景

本文继续上一篇中提到的问题–python程序在编码的时候会涉及到一些中间过程的操作,其中有关ascii,unicode,utf8等等编码相关的知识,这里我们对以上提到的概念作一个详细整理,以及自己的理解

编码规则(字符集)

ascii

  ascii值是早期的美国人制定的一个规则,规定了英文以及一些字符的二进制表示用于在电脑上编码,它用了一个bit位中的前7个高低位—2^7表示128中不同的状态,这128中状态就对应了常用的符号,比如:a,b,c,d…..空白符….等等

  后面计算机到了其他国家,那些国家可能本国语言所用到的字符多于128种,比如法国,他们除了基础的128个字符外,还需要表示口语中的音节,于是他们自己制定了一种编码方式,能表示英文语境中所没有的音节等其他东西,用到了那一个bit位中的第八个高低位,总共能表示256种状态

  类似的,中国也是一样,用来表示中文汉字,制定了自己的编码方式gbk等等

unicode

  unicode是一种字符集,像它的名字一样—宇宙,它规定了全宇宙上所有符号的二进制表示,任何一个符号,都有其对应的唯一的二进制表示
  比如:大写的A用unicode表示是:U+0041
    其中每一位是用十六进制显示的,十六进制的41 == 十进制的65(16^1 × 4 + 16^0 × 1 = 65)
  图一

为什么要搞这么一种统一的编码方式?

  上面提到的,世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码

  最常见的一个乱码就是电子邮件,就是因为发信人和收信人使用的编码方式不一样,比如你在中国给美国人发一份邮件,美国人用ascii,中文用gbk,我们的gbk根本无法用ascii编码,所以会出现乱码

  可以想象,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是unicode,就像它的名字都表示的,这是一种所有符号的编码

  但一开始网络的不活跃导致了大家都在用局域网,互相之间通信很少,所以对”统一”这个需求也不是很强烈,一直到后面的互联网出现,这才迫切需要一个统一的编码,方便全世界的人民互相交流,这就是接下来我们要说的utf8

utf8的编码方式

1
2
3
4
5
6
7
Unicode符号范围 | UTF-8编码方式
(十六进制)   | (二进制)
--------------------+---------------------------------------------
0000 0000-0000 007F | 0xxxxxxx
0000 0080-0000 07FF | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-001F FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

跟据上表,解读UTF-8编码非常简单
  如果一个字节的第一位是0,则这个字节单独就是一个字符(显然就是ascii)
  如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。

  再给出一张图,我们来说说上面这个,从表中易得出 严 字属于第三列

1
2
3
4
5
6
7
Unicode符号范围 | UTF-8编码方式
(十六进制) (十进制) | (二进制)
----------------------------------------------------------------------------------------------------
0000 0000-0000 007F (0-127) | 0xxxxxxx
0000 0080-0000 07FF (128-2047) | 110xxxxx 10xxxxxx
0000 0800-0000 FFFF (2048-65535) | 1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF (65536-1114111) | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

我们看上图左边的表:
  第一行:0000 ~ 0000 007F ,换算成十进制则是:0~127
  第二行:0080 ~ 0000 07FF,换算成十进制则是:128~2047

其他的同理计算即可

我们可以根据上面规则来得出字符所处的位置,接下来就该编码了

unicode编码成utf8

  “严”字unicode为:4E25,换算成十进制大概是2万多,知道其在第三行,即应该按照1110 xxxx 10xx xxxx 10xx xxxx编码

  从”严”字的最后一个二进制位(也就是最右边的位)开始(其二进制位为:1001110 00100101)

  依次从后往前,将二进制的所有位按照次序:插入到unicode的x上,插入完了,余下的部分置0,即得到下面的

1
2
3
1110 xxxx 10xx xxxx 10xx xxxx
1110 0100 1011 1000 1010 0101

  其中最右边的0是填充的,其他对应着xx的部分,你可以试着抽出来,就是”严”字的二进制表示

最后,我们得到
  “严”的UTF-8编码是”11100100 10111000 10100101”,转换成十六进制就是E4B8A5

ascii和unicode的矛盾

  既然你说要统一,那么我ascii是不是也要按照unicode的方式去表示,去存储呢?

  比如英文的A用unicode表示是U+0041,那么这么一来,原本ascii一个字节就能存储完的元素,这时候在任何地方存都需要开辟4个字节,这四个字节中的前三个字节上置放24个0作占位符,这对空间来讲是一种极大的浪费

  如果你的母语是英文,而被强迫用unicode编码的电脑,那简直就是独裁,存储一个元素本来一位就行了,但活生生需要拿出三个空和那个实际的存放字符的位一起,只是为了和世界统一,所以这不被大家所接受

  所以初期unicode完全没有被推行起来也是有原因的,我们最好的解决方法是统一按照utf8的形式编码,这样既解决了内存浪费问题,也方便全世界统一用一个编码去交流

End

到这里,我们完完全全搞明白了这些词的意思:
  unicode,ascii,utf8

那么回到我们之前的问题,现在有了结论
  一.python一般会根据你拿到的字符串来自动判断它应该用什么编码,比如:全英文—底层使用ascii编码,含有中文—底层采用unicode编码

  二.为什么要在python脚本中指定defaultcoding,是因为在encode和decode方法中会默认用到defaultcoding,而使用中文,无法被decode(“ascii”),所以需要指定为utf8—即decode(“utf8”)

  三.ascii的编码的字符可以转换成unicode,但unicode对于ascii来说毫无意义,只是加了3个bit位的0占位符

  四.unicode编码的字符无法转换成ascii,ascii只能表示前128位的字符,这是世界通用的128个基础字符

  五.要解决乱码,将所有编码统一以utf8的格式编码

  六.前128个基础字符要特别注意,ascii就是ascii,它在utf8中不会用unicode去编码,依旧保持其ascii只占一位的特点,所以你ascii的decode(“ascii”)和decode(“utf8”)得到的都是unicode,并且只能被编码成ascii
  
  你可以尝试在不加defaultcoding = “utf8”的情况下,对一个全部是英文的unicode字符串进行encode(“utf8”),得到的还是ascii,也就是说—utf8包含了ascii编码,即上面提到的utf8编码的第一行

还有一些具体到落地的字节存储规则我没讲,后续看看遇到了或者有时间再作整理,编码的问题希望以后不要再浪费时间了,best wishes

参考博文:
  http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
  http://www.cnblogs.com/leesf456/p/5317574.html


如你在本文发现有什么错误,希望给我发邮件或者留言指出,tingyunsay@gamil.com,我将不尽感激~~