字符集与编码

encode,deconde,character-set

Posted by elmagnifico on December 1, 2017

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