您的位置:首页 > 编程语言 > Python开发

字符编码笔记汇总:ASCII、GBXXXX、Unicode、UTF-8等

2017-09-16 21:07 295 查看

字符编码笔记汇总:ASCII、GBXXXX、Unicode、UTF-8等

版权声明:本文系个人经多处资料学习、吸收、整理而得,如需转载,请注明出处:作者名+链接。

内容说明:本系列内容大致包括基本概念(字符集、字符编码)、常用字符集和字符编码(ASCII、GB系列、UniCode等)、乱码原因及解决方案、Python内字符编码的相关处理。 (含大小端说明)

关键词:字符编码,ASCII、Unicode、utf-8、乱码与解决办法、Python

1. 基本概念

首先,说下编码解码:计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为”编码”;反之,将存储在计算机中的二进制数解析显示出来,称为”解码”。在解码过程中,若使用了同编码方式不同的编码规则饿,则可能导致解析错误或者乱码。

其次理解字符集字符编码

字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。一般来说,不同的字符集有不同的对应字符编码规则,因此也常会看到有人说:字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。

字符编码:是一套法则,在符号集合(人们表达信息的方式)与数字系统(计算机存储和处理信息的方式)之间建立对应关系,也就是说字符编码就是将字符转换为计算机可以接受用0、1表达的数。

2. 常用字符集和字符编码

常见字符集有:ASCII字符集、GB2312字符集、BIG5字符集、Unicode字符集等,每种字符集有对应的一种或多种编码方式。

ASCII字符集&ASCII编码

ASCII(American Standard Code for Information Interchange,美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,而其扩展版本EASCII则可以勉强显示其他西欧语言。它是现今最通用的单字节编码系统(但是有被Unicode追上的迹象),并等同于国际标准ISO/IEC 646。

ASCII字符集:主要包括控制字符(回车键、退格、换行键等)、可显示字符(英文大小写字符、阿拉伯数字和西文符号),共128个字符。

ASCII编码:将ASCII字符集转换为计算机可以接受的数字系统的数的规则。使用7位(bits)表示一个字符,共128字符;但是7位编码的字符集只能支持128个字符,为了表示更多的欧洲常用字符对ASCII进行了扩展,ASCII扩展字符集使用8位(bits)表示一个字符,共256字符。编码表可参见[1].

缺点:只能显示26个基本拉丁字母、阿拉伯数目字和英式标点符号,因此只能用于显示现代美国英语(而且在处理英语当中的外来词如naïve、café、élite等等时,所有重音符号都不得不去掉,即使这样做会违反拼写规则)。而EASCII虽然解决了部份西欧语言的显示问题,但对更多其他语言依然无能为力。因此现在的苹果电脑已经抛弃ASCII而转用Unicode。

简体中文字符集&gbXXXX编码

由于ASCII码只支持英文,为了显示中文,必须设计一套编码规则用于将汉字转换为计算机可以接受的数字系统的数。

为此,天朝专家把那些127号之后的奇异符号们(即EASCII)取消掉,规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE,这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在ASCII里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的”全角”字符,而原来在127号以下的那些就叫”半角”字符了。

上述编码规则就是GB2312,其对应的是中国国家标准简体中文字符集,1981年5月1日实施。中国大陆几乎所有的中文系统和国际化的软件都支持GB2312。GB2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆99.75%的使用频率。但对于人名、古汉语等方面出现的罕用字,GB2312不能处理,这导致了后来gbk编码(可对应到gb18030字符集《信息技术 中文编码字符集》)的出现。

gb18030特点

采用多字节编码,每个字可以由1个、2个或4个字节组成。

编码空间庞大,最多可定义161万字符

支持中国国内少数民族的文字,无需动用造字区

收录范围包含繁体汉子以及日韩汉子

繁体中文字符集&Big5编码

Big5是繁体中文中最常用的电脑汉子字符集标准,共收录13,060个汉字,属中文内码。Big5码是一套双字节字符集,使用了双八码存储方法,以两个字节来安放一个字。第一个字节称为”高位字节”,第二个字节称为”低位字节”。”高位字节”使用了0x81-0xFE,”低位字节”使用了0x40-0x7E,及0xA1-0xFE。

Unicode字符集&utf-X编码

像天朝一样,为适合当地语言和字符,需要设计和实现类似GB232/GBK/GB18030/BIG5的编码方案。但这样各搞一套编码规则,在本地使用没有问题,一旦出现在网络中,由于不兼容,互相访问就出现了乱码现象。

为了解决这个问题,Unicode(也称之为万国码、统一码)诞生了,为表达任意语言的任意字符而设计,基于通用字符集(Universal Character Set)的标准发展而来,也可以理解为是一种将世界上所有符号都纳入其中的字符集,每一个符号都给予一个独一无二的编码。它使用4字节的数字来表达每个字母、符号,或者表意文字(ideograph)。每个数字代表唯一的至少在某种语言中使用的符号。需要理解的是,Unicode是字符集,UTF-32/ UTF-16/ UTF-8是对应Unicode的三种字符编码方案,也可以理解成Unicode的实现方式。

UTF-32:对每个字符都是用4字节,就空间而言,是非常没有效率的。这种方法有其优点,最重要的一点就是可以在常数时间内定位字符串里的第N个字符,因为第N个字符从第4×Nth个字节开始。虽然每一个码位使用固定长定的字节看似方便,它并不如其它Unicode编码使用得广泛。

UTF-16:考虑到一般大多数人不会用到超过前65535个以外的字符,UTF-16将0–65535范围内的字符编码成2个字节,如果真的需要表达那些很少使用的”星芒层(astral plane)”内超过这65535范围的Unicode字符,则需要使用一些诡异的技巧来实现。UTF-16编码最明显的优点是它在空间效率上比UTF-32高两倍,因为每个字符只需要2个字节来存储(除去65535范围以外的),而不是UTF-32中的4个字节。并且,如果我们假设某个字符串不包含任何星芒层中的字符,那么我们依然可以在常数时间内找到其中的第N个字符,直到它不成立为止这总是一个不错的推断。

UTF-8:针对Unicode的可变长度字符编码(定长码),也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与ASCII兼容,这使得原来处理ASCII字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。

UTF-8使用一至四个字节为每个字符编码,编码规则简单来说就是两条:

1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。

2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。

具体编码范围和对应编码如下所示:

0000 0000-0000 007F | 0xxxxxxx

0000 0080-0000 07FF | 110xxxxx 10xxxxxx

0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx

0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

特点

1)在处理经常会用到的ASCII字符方面非常有效,UTF-8是ASCII的一个超集。因为一个纯ASCII字符串也是一个合法的UTF-8字符串,所以现存的ASCII文本不需要转换。在处理扩展的拉丁字符集方面也不比UTF-16差。对于中文字符来说,比UTF-32要好。

2)UTF-8不再存在字节顺序的问题,一份以utf-8编码的文档在不同的计算机之间是一样的比特流。UTF-8字符串可以由一个简单的算法可靠地识别出来。就是,一个字符串在任何其它编码中表现为合法的UTF-8的可能性很低,并随字符串长度增长而减小。

3)UTF-8和UTF-16都是可扩展标记语言文档的标准编码。所有其它编码都必须通过显式或文本声明来指定。

4)任何面向字节的字符串搜索算法都可以用于UTF-8的数据(只要输入仅由完整的UTF-8字符组成)。但是,对于包含字符记数的正则表达式或其它结构必须小心。

5)缺点:utf-8在Unicode字符串中不可能由码点数量决定显示它所需要的长度,或者显示字符串之后在文本缓冲区中光标应该放置的位置,组合字符、变宽字体、不可打印字符和从右至左的文字都是其归因。此外,因为每个字符使用不同数量的字节编码,所以寻找串中第N个字符是一个O(N)复杂度的操作 — 即,串越长,则需要更多的时间来定位特定的字符。同时,还需要位变换来把字符编码成字节,把字节解码成字符。

另外,UTF-32和UTF-16还有一个不明显的缺点跟大端小端相关:不同的计算机系统会以不同的顺序保存字节,这取决于该系统使用的是大端(big-endian)还是小端(little-endian)。为了解决这个问题,多字节的Unicode编码方式定义了一个字节顺序标记(BOM,Byte Order Mark),它是一个特殊的非打印字符,你可以把它包含在文档的开头来指示你所使用的字节顺序。对于UTF-16,大端对应的字节顺序标记是U+FEFF,小端序对应的是FFFE。

补充说明:大端小端

小端:较高的有效字节存放在较高的的存储器地址,较低的有效字节存放在较低的存储器地址。

大端:较高的有效字节存放在较低的存储器地址,较低的有效字节存放在较高的存储器地址。

简单理解就是,大端存储类似人的正常思维,小端存储机器处理更方便。比如32bit整型0x12345678,大端存储为(低地址)12 34 56 78(高地址),小端存储为78 56 34 12。

ANSI编码

为使计算机支持更多语言,通常使用0x800~xFF范围的2个字节来表示1个字符。不同的国家和地区制定了不同的标准,由此产生了GB2312,BIG5,JIS等各自的编码标准。这些使用2个字节来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码。在简体中文系统下,ANSI 编码代表 GB2312 编码,在繁体中文操作系统下,ANSI 编码代表 Big5 编码。在非 Unicode 环境下,由于不同国家和地区采用的字符集不一致,很可能出现无法正常显示所有字符的情况。

通常Windows环境下,txt文件默认为ANSI编码,即Windows code pages,它将根据当前locale选定具体的编码,比如若为纯英文字符,则对应为ASCII码,若是简体中文,则默认对应为gb2312,而繁体中文默认对应Big5。

但未来的趋势应当是以Unicode字符集来保存字符,而且用户采用Unicode下的编码格式来保存的话,处理起来也会有速度上的优势。

3. 乱码原因和解决办法

对于是否乱码的判断关键在于:判断当前编码解码方式是否一致以及该字符集是否兼容该文字(比如中文)。也就是说,乱码的出现是因为编码和解码时用了不同或者不兼容的字符集。比如说一个用UTF-8编码后的字符,用GBK去解码。由于两个字符集的字库表不一样,同一个汉字在两个字符表的位置也不同,最终就会出现乱码。再比如说ACSII字符集不支持中文编码解码,输出中文时会出现乱码。

其他说明:加入文本中所有字符都在ASCII范围内,即英文字符等,那记事本保存的ANSI编码和ASCII或UTF-8是一样的。

乱码常见场景及解决办法

比如

1)将需要utf8mb4编码的Emoji表情(占四字节)存入MySQL数据库(一般默认为utf-8(3字节))中会报错;

解决办法:将MySQL字符集切换至utf8mb4,或者 存入前将Emoji字符串解码为一段特殊的文字编码再存入,而从数据库取出时将这段特殊文字编码转换为Emoji显示,一般常通过解码为Unicode作为中转转换。

2)Windows桌面系统下,文本文件打开后乱码

解决办法:将文件编码方式修改成合适的编码方式,比如可通过Notepad++或者windows自带记事本——另存为时,选择新的编码方式:ANSI/Unicode(utf-16,直接用两个字节存入字符,小端格式)/Unicode big endian(大端格式)/UTF-8(这里有人提到这是为带BOM的UTF-8,但如果是为了跨平台,最有效的办法就是使用utf-8名单是否使用BOM则未必需要很纠结,因为现在的编辑器大多可以很好地处理BOM,比如VIM,像Python脚本也是可以很好地处理BOM),然后再保存即可。

3)下一节中提到的Python内字符编码的相关处理

总的来说,解决办法就是根据文件或者字符的编码方式/字符集设定相同的/兼容的字符编码方式来进行解码。那如何判别该文件是什么编码方式呢?Windows记事本的做法是在TXT文件的最前面保存一个标签,如果记事本打开一个TXT,发现这个标签,就说明是unicode。标签叫BOM,如果是0xFF 0xFE,是UTF16LE,如果是0xFE 0xFF则UTF16BE,如果是0xEF 0xBB 0xBF,则是UTF-8。如果没有这三个东西,那么就是ANSI,使用操作系统的默认语言编码来解释。

4. Python内字符编码的相关处理

获取和修改系统默认字符编码的方法:

import sys
print sys.getdefaultencoding()   #输出结果为ascii #或者
reload(sys)
sys.setdefaultenconding("utf-8")  #修改系统默认编码为utf-8
print sys.getdefaultencoding()   #如果没有修改过默认编码,输出结果为utf-8


自行设定文件编码方式,比如设编码方式为utf-8,在文件首行敲上如下代码,若是要设为其他编码方式,则把utf-8修改成想设的编码方式(如ascii、gbk)即可。一般首行我们都会声明该文件编码方式为utf-8,这里注意系统默认编码不会随之改变。

# -*- coding: utf-8 -*-
#或者
#coding=utf-8    #若是Windows下自带BOM且想去掉BOM字符,可修改encoding为utf-8_sig / utf_8_sig


文件内声明的字符串的编码方式跟之前保留的文件编码方法相关,若保留为ascii码,则字符串为ascii编码,若为utf-8,则字符串为utf-8编码,这里往往在中文字符前加上u用来声明该字符串为utf-8编码;

确认某个字符串是哪种编码类型:

>>>import chardet #需要自行安装chardet编码检测包 pip install detect (Anaconda需要在其对应的Scripts目录下打开命令行)
>>>str = "string"
>>>chardet.detect(str2)
{'confidence': 1.0, 'language': '', 'encoding': 'ascii'}


设定某字符串的编码方式,可通过decode(解码)、encode(编码)实现:

#英文
print "string".decode("utf-8")
print "string".encode("gbk")
print "string".decode("utf-8").encode("gbk")
#中文
print "中文"   #是否乱码,根据当前保留的字符编码环境而异
print u"中文"  #文件编码方式设定为utf-8时,加u确保不会乱码
print "中文".decode("utf-8")  #等同于unicode('中文',’utf-8‘),获取到unicode对象
#print "中文".encode("gbk") #否则,会报错
print "中文".decode("utf-8").encode("gbk") #各字符编码方式间的转换需要通过unicode对象作为中转,即先decode解码为unicode再重新编码,因原字符串是utf-8编码,此处强行转为gbk编码解释,会得到乱码输出
#编码方式理解
s="水壶"  #对应utf-8编码为\xe6\xb0\xb4\xe5\xa3\xb6,可在命令行窗口输入s查看,只有在utf-8下才有实际意义
>>>s
'\xe6\xb0\xb4\xe5\xa3\xb6'
print unicode(s,"big5") #瘞游ㄥ
print unicode(s,"gbk") #姘村6
print unicode(s,"gb2312") #姘村6
print unicode(s,"utf-8") #水壶


对于是否乱码的判断关键在于:判断当前编码解码方式是否一致以及该字符集是否兼容该文字(比如中文)。比如IDE和控制台报错,原因是print时,编码和IDE自身编码不一致导致。输出时将编码转换成一致的就可以正常输出。

理解Python2.X环境中str和unicode

str是字节串,由unicode经过编码(encode)后的字节组成的;声明方式:s =’中文’ 或者 s = u’中文’.encode(‘utf-8’)

unicode才是真正意义上的字符串,由字符组成;声明方式:s = unicode(‘中文’,’utf-8’) 或者 s=’中文’.decode(‘utf-8’)或者s=u’中文’,可通过type(s)查看其类型

搞明白要处理的是str还是unicode, 使用对的处理方法(str.decode / unicode.encode);一般建议输入处全部转为unicode,而后处理,输出时再转成目标编码,即终极原则:decode early, unicode everywhere, encode late。

其他:Python3中字符串编码

3.x中将字符串和字节序列做了区别,字符串str是字符串标准形式与2.x中unicode类似,bytes类似2.x中的str有各种编码区别。bytes通过解码转化成str,str通过编码转化成bytes。

Python 3的源码.py文件 的默认编码方式为UTF-8,所以,对于Python 3.x来说,编码问题已经不再是个大的问题,基本上很少遇到编码异常,但不再支持u中文的语法格式。而Python2中.py文件默认编码方式为ASCII,一般通过在首行放置编码声明(常用utf-8)。

参考文献:

字符集和字符编码(Charset & Encoding)

字符编码笔记:ASCII,Unicode和UTF-8——阮一峰

Windows 记事本的 ANSI、Unicode、UTF-8 这三种编码模式有什么区别?

PYTHON-进阶-编码处理小结

python字符串编码及乱码解决方案

尾言:文中若有笔误或不正确的地方,烦请包涵并指出;若有其他想法或意见也欢迎与我(wangdaiyin_pku@163.com)交流,谢谢。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息