Foreword
之前遇到了各种类似于下面的问题
UnicodeEncodeError: 'gbk' codec can't encode character ....
UnicodeEncodeError: 'ascii' codec can't encode characters in position 0-1....
每次都是临时搜一下找一下解决办法,然后弄过来,好用了就不管了,下次再遇到再搜一下…
所以一直没搞清楚到底是为啥出错了,现在来梳理一下会用到的各种编码格式以及对应的字符集
字符集与编码
首先要分清楚什么是字符集,什么是编码格式
字符集
字符集,很容易理解他就是一本字典,每一个符合(英文,中文,数学符号等等),他们都与某一个数字对应,从而构成了字符集.
比如在ASCII中 字符’0’ 对应 数字48 , 字符’a’ 对应 数字 97 ,类似这样的一组映射关系构成了一个字符集
编码格式
编码格式,其本质上是描述如何存储对应的字符集。比如ASCII中’a’对应的是数字97,那么其存储在内存/硬盘中也是用二进制表示出来的97。当然有很多种编码格式,不同的编码格式可能存储的时候有不同转换,所以编码规定的是字符集如何存储在介质中。
分清了这两个东西才能明白下面的内容。
ASCII与其扩展
首先ASCII编码(非扩展)其即是字符集也是编码格式,他同时定义了两个。
他在存储的时候只用了1byte,并且其也只用了低7位,开始时只定义了128个字符。
标准的ASCII只是英语系国际可以使用,对于其他国家而言这样的字符根本无法表达本族语言,所以出现了其扩展。
而ASCII标准下刚好又只用了128个字符,所以其他国家就接过来自定义了自己国家的字符,从而有了不同国家的不同扩展,但是这样依然不够用,比如汉字就根本无法用128个字符表达出来,针对这种情况呢,自然就有了新的字符集GBK
GBK
GBK,它即是字符集也是编码格式,具体的我就不说了,只需要知道他是用两个字节,也就是16bits,最多能表示65535个字符(实际上没用这么多)。而且呢,GBK也兼容ASCII,从而可以在一个文章中既有汉字有有英文并且不会发生错乱的情况。
但是GBK只是用来兼容了中文和英文,其他国家的语言也同样需要这样的规范,这样就出现了,一个国家一个字符集(兼容ASCII),可能在存储的时候同样的一个数字,但是实际上表示含义在不同国家并不相同,这样就很容易引起误会,因为别人并不知道你这个文件到底用哪国的字符集和编码形式。
Unicode
基于上面的需求,从而出现了大一统,那就是Unicode,Unicode就是一个单纯的字符集,他不仅仅收录了英文,中文,还收录了绝大多数国家的语言符号于其中,其使用了2的32位(4字节)的大小来存储字符,从而保证了这个字符够大,基本不可能用的完.
这里只规定了字符集,可以看到如果真的要存储Unicode编码的字符,需要四字节,但是不同的国家的字符,比如英文完全不需要四个字节来存储,这样会浪费非常多的空间,所以字符集还需要搭配编码格式来使用,下面来介绍。
UTF-8/16/32
这里的UTF就是对应的Unicode的编码方式,其中UTF-8所使用的编码规则是按照最小单位为字节进行编码,比如英文只需要一个单位,但是汉字等可能就需要3个字节(所以有时候文件会变大)
-
UTF-8是兼容ASCII的,本身是可以变长的,最多是6字节,最少是1个字节,空间利用率高,所以使用非常广泛.
-
UTF-16则是使用最小单位是2或4个字节来编码,存储的时候自然会遇到大端小端的问题,其是不兼容ASCII的。
-
UTF-32则是使用最小单位是4个字节来编码,那么存储的时候依然也有大小端问题,但是对于code来说却最方便了,因为大小都是一致的,每4个字节解析一下就好了,其空间利用率则是非常低的
目前基本多数情况下都是用UTF-8来处理各个国家的编码,只有极少数情况下多个国家的字符混合使用的时候可能会用到更大UTF32?
大端小端
多个字节的时候,字节在存储介质中排序是1,2,3,4 还是 4,3,2,1的问题
ANSI
windows下的记事本,其默认编码格式是ANSI。ANSI的编码格式比较特殊,他是根据你的系统语言来决定文本使用什么格式来编码的。比如如果是中文环境,那么默认使用的就是GBK的编码方式(兼容了ASCII)。如果你是使用的英文环境,那默认就是ASCII的编码。如果是其他的日语或者德语或者什么语系,自然也会应用对应语系的统一编码格式。所以ANSI是一个根据你环境来决定编码格式的,如果你将中文环境下的ANSI给到日语环境下,那么就会发现其中文部分全部成了乱码。
ANSI是记事本的默认编码格式,之所以是默认自然有他历史原因。当年ASCII字符集刚出的时候根本没有考虑到其他语言的情况,而后来有了各国统一编码的时候,Unicode还未诞生,为了兼顾各个国家的语系,ANSI是最好的选择了。但是平时最好是不要使用ANSI的编码格式,这种编码在遇到一些国际情况的时候就会出现一些奇怪的字符。
BOM
没有Unicode的时候根据系统语言就能决定记事本的文件格式用什么来解读,但是当有了Unicode的时候就会出现文本的编码对应不完全的情况了。那么这个时候就需要有一个标记用来区分到底是什么格式的编码,以及大端小端的序列:
- BOM,Byte Order Mark,字节序标记
编码表示 | 十六进制 |
---|---|
UTF-8 | EF BB BF |
UTF-16(大端序) | FE FF |
UTF-16(小端序) | FF FE |
UTF-32(大端序) | 00 00 FE FF |
UTF-32(小端序) | FF FE 00 00 |
其他格式 | …. |
ANSI | 没有对应字节 |
有时候将UTF-8转化为ANSI格式的时候,你都会发现文本开头出现了三个乱码,这个乱码自然就是BOM了,将ANSI转为UTF-8的时候,也会出现转换为带BOM或者不带.
当然之所以以前没有BOM的原因,就是因为当初要求记事本/文件的内容就应该能直观读取出来,而不含有任何隐藏的无法直接显示的字符。随着时间的进步,这个规则不可避免的要被打破,于是就出现了各种gbk或者UTF-8编码的问题。
到底保存的时候带不带BOM,我认为应该带,在目前的情况下各种文本格式都存在,如果不带BOM,出现了乱码格式是非常麻烦的,根本不知道用什么去解释这个文本,而大部分的编辑器都可以自动识别BOM,并且处理成对应的格式显示.
other
回到之前的编码问题,多数情况下都在将使用Unicode字符集的文本转换为GBK或者其他较小的字符集的时候会出现字符丢失的情况。又或是使用了GBK的编码方式来读取Unicode的文本,反之亦然,所以要处理文本前,先弄明白要处理的到底是什么(或者规定好输入文本类型),再去处理.
至于自动识别编码的方案还是有不少,比如有的是靠根据前若干字符进行解码,如果符合某一个字符集则使用该字符集进行整个解码,这种方式也时常会出错,还有靠自然语言等等方式来进行解码识别的,但是这种要的代价都太高了,远不如一开始就规范好来的快.
Summary
除非有一天全世界都只使用一种文本格式,否则编码的问题会一直存在。所以建议使用UTF-8,BOM;在文本处理的时候自然也需要注意到这里的问题,当然有的时候不仅仅处理文本就可以了,有很多编辑器可能默认是ANSI类型的,所以编辑器自身可能也需要修改编码选项才可以.
Quote
https://www.cnblogs.com/fnng/p/5008884.html
http://www.jb51.net/article/64816.htm
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html http://www.imkevinyang.com/2009/02/%E5%AD%97%E7%AC%A6%E7%BC%96%E8%A7%A3%E7%A0%81%E7%9A%84%E6%95%85%E4%BA%8B%EF%BC%88ascii%EF%BC%8Cansi%EF%BC%8Cunicode%EF%BC%8Cutf-8%E5%8C%BA%E5%88%AB%EF%BC%89.html
https://www.cnblogs.com/tingyugetc/p/5727383.html
https://www.zhihu.com/question/20312182
https://www.zhihu.com/question/20167122/answer/23328115
https://www.zhihu.com/question/20650946/answer/15751688