字符集和编码

编程/技术 2019-06-06 @ 16:40:58 浏览数: 44 净访问: 34 By: skyrover

本博客采用创作共用版权协议, 要求署名、非商业用途和保持一致. 转载本博客文章必须也遵循署名-非商业用途-保持一致的创作共用协议


平时工作中发现不少同事对字符集和编码相关的问题仍然是一脸懵逼,所以在这里我就把这个问题细致的厘清。

基本概念

有些基本概念是要澄清的,那就是字符集字符编码字符编码方式glyph

首先文字的字形被称为glyph,这个概念在理解字符集和编码中无关紧要,所以了解一下就好。

其次,字符集,因为文字是无限多的,除了正经常用文字,还可能包含符号,象形文字啥的,所以有必要规定一个字符集,用来描述可以表示的文字集合。比如ASCII字符集,Unicode字符集。Unicode是指字符集,而不是编码方式

字符集中的每一个字符都会有一个编码,这就叫做字符编码。

计算机中用整数值来表示文字编码的方式称为字符编码方式。一个字符集可能有多种字符编码方式,比如Unicode字符集会有utf-8, UTF-16BE, UTF-16LE, UTF-32BE, UTF-32LE。这里面BE,LE是Big end(大端),Little end(小端)的简写,这些概念后面在详细说明字符编码方式的时候会介绍。

编码演化

ASCII编码由7位二进制数组成,一共可以表示128个字符,但是一个字节是8位,多出的一位在早期是用作校验码的,因为那时候通信还不是那么可靠。可能也正是因为这个原因,ASCII码坚持到了现在。

后来需要支持其他的语言,比如欧洲语系,当时的办法就是扩展ASCII编码,将第8位也加入了编码。有小伙伴会问,那不用校验码了吗?答案是不需要了,因为通信变得可靠了。

到了亚洲,扩展ASCII码也不够用啊,常用汉字有几千个之多,1个字节是没法表示了,所以就出现了用多字节表示一个字的方法。我们国家对应的字符集有GB2312(EUC-CN), Big5。其中括号中的是字符编码方式。

再到后来,每个国家都会有自己的编码方式,这就很难受了,所以就出现了统一的Unicode字符集及其对应的编码方式,一开始Unicode使用16位来编码字符,总共有65535个字符,但是这点也是不够的,所以就出现一个编码对应了多个字符。其次,16位就需要考虑大端小端问题。大端就是高字节在低位,方便阅读。小端是低字节在低位,方便CPU处理。一般来说网络字节序是大端的,计算机内部是小端的。但是这有个影响就是文件必须要存储一个标记来标志是大端还是小端的,所以有一个叫做BOM标志的东西放在文件头,用来判别文件的字节顺序。

Unicode中的编码方式

常见的有三种:UTF-8UTF-16UTF-32

UTF-32

用4字节固定长来编码,比较耗内存,所以用的也不是很广泛。

UTF-16

如果能用16位来表示,就用16位表示,如果超过的话,使用代理对的两个16位码的组合来表示。这个也不常用,而且快被淘汰了。

UTF-8

这个是最常用的编码方式,并且是变长的编码方式,与ASCII码兼容。它的编码方式大概是这样的:

  1. 如果第一字节第8位上不是1,则表示1字节文字,也就是对应了ASCII码
  2. 通过第一字节位的样式来确定后面还有几个字节。
  3. 多字节的情况,除了第一字节,其他字节都是以10开始的

比如:

字符 UTF-8编码(2进制)
1 00110001
a 01100001
11100100 10111000 10000000

Python中的文字处理

乱码

总结一下,乱码的可能原因有:

  • 字符编码方式错误

因为文本数据没有附加编码方式,所以很容易出现编码方式导致的乱码情况。可以通过下面的方法来确定编码方式:特定的字节排列和文字出现频率。

HTTP文本一般可以附加编码方式,所以不会出现编码方式导致的乱码。XML更是强制要求编码方式,默认就是UTF-8

  • 没有字体

计算机想要展示某个字,除了正确解析出字符编码,还有能找到对应字体,如果没有字体,那就不能展示出来,一般会以空心框来展示,俗称豆腐。

  • 文字集不同

如果编码方式一样,但是字符集不同的话,互相转换也可能导致乱码,所以也会用空心框来展示。

  • 字节顺序错误

一般是在UTF-16,UTF-32这种需要考虑字节顺序的编码方式上。而UTF-8是以字节位单位的编码方式,所以没有字节顺序问题。

编程语言如何处理文字

一般有两种方式:

  • 以变换为前提的UCS(Universal Character Set 泛用字符集),指程序中所处理的共同文字集(及字符编码方式),输入输出的时候,编程语言将文本数据变成UCS,内部对文本数据进行统一处理。
  • 原封不懂处理的CSI方式(Character Set Independent,字符集独立),不对各种文字集进行任何变换,直接原封不动进行处理。

大部分编程语言都是使用UCS方法进行编码处理的,比如Python,编译的时候可以指定--enable-unicode=ucs2或者--enable-unicode=ucs4,有以下区别:

ucs4
>>> import sys
>>> print sys.maxunicode
1114111

ucs2
>>> import sys
>>> print sys.maxunicode
65535

ucs2和utf-8转换也是比较简单的,可以参考这篇文章:UCS-2编码与UTF-8编码

也就是说如果输入输出都是utf-8编码,那就需要先从utf-8转换为ucs2,输出时候再转换位utf-8

而使用CSI方式的编程语言很少,目前Ruby是这样的。

Python2和Python3的文字处理

在Python2中,str为bytes类型表示二进制数据的字节串,而unicode表示Unicode字符串。Python3中,二进制是bytes,文本是str(Unicode字符串)。

首先在Python2中,文件开头#coding=utf-8声明这句话就是告诉python解释器(默认ACSII编码方式)解释文件声明下面的内容按utf8编码。

Python2中s='中文's是个str对象,str其实是字节串,它是unicode经过编码后的字节组成的序列,而s=u'中文'是一个Unicode字符串。

因为Python2解释器会默认将str进行decode,编码格式就是默认环境的编码格式,所以如果这行代码在python解释器中输入并且运行,那么s的格式就是解释器的编码格式;

如果这行代码是在源码文件中写入、保存然后执行,那么解释器载入代码时就将s初始化为文件指定编码(比如py文件开头那行的utf-8);

对于unicode对象字符串,Python内部可能是两个字节(ucs2),也可能是四个字节(ucs4)。str是unicode这个字符串经过编码(utf8,gbk等)后的字节组成的序列。

>>> s = '中文'
>>> s
'\xe4\xb8\xad\xe6\x96\x87'
>>> u'中文'.encode('utf-8')
'\xe4\xb8\xad\xe6\x96\x87'
>>> u'中文'
u'\u4e2d\u6587'

py2编码的最大特点是Python 2 将会自动的将bytes数据解码成unicode字符串。Python2悄悄掩盖掉了byte到unicode的转换,让程序在处理ASCII的时候更加简单。你复出的代价就是在处理非ASCII的时候将会失败。

默认编码方式为ASCII
>>> s + u'中文'
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xe4 in position 0: ordinal not in range(128)
>>> import sys
>>> sys.getdefaultencoding()
'ascii'
>>> reload(sys)
<module 'sys' (built-in)>
>>> sys.setdefaultencoding('utf-8')
>>> sys.getdefaultencoding()
'utf-8'
>>> s + u'中文'
u'\u4e2d\u6587\u4e2d\u6587'

而Python3中字节序列和字符串有了更加清晰的区分,所以如果你想要用一个 byte 字节串和一个unicode相链接的话,你将会得到一个错误,不管你包含的内容是什么。


点赞走一波😏


评论

提交评论
current-liu
2019-06-06 @ 17:00:51 中国 陕西 西安

博主写的非常666

Mac OS X:10.14.0-Other Chrome:74.0.3729