版权声明:本文系个人经多处资料学习、吸收、整理而得,如需转载,请注明出处:作者名+链接。
内容说明:本系列内容大致包括基本概念(字符集、字符编码)、常用字符集和字符编码(ASCII、GB系列、UniCode等)、乱码原因及解决方案、Python内字符编码的相关处理。 (含大小端说明)
关键词:字符编码,ASCII、Unicode、utf-8、乱码与解决办法、Python
首先,说下编码和解码:计算机中储存的信息都是用二进制数表示的;而我们在屏幕上看到的英文、汉字等字符是二进制数转换之后的结果。通俗的说,按照何种规则将字符存储在计算机中,如’a’用什么表示,称为”编码”;反之,将存储在计算机中的二进制数解析显示出来,称为”解码”。在解码过程中,若使用了同编码方式不同的编码规则饿,则可能导致解析错误或者乱码。
其次理解字符集和字符编码:
字符集:是一个系统支持的所有抽象字符的集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。一般来说,不同的字符集有不同的对应字符编码规则,因此也常会看到有人说:字符集就规定了某个文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。
字符编码:是一套法则,在符号集合(人们表达信息的方式)与数字系统(计算机存储和处理信息的方式)之间建立对应关系,也就是说字符编码就是将字符转换为计算机可以接受用0、1表达的数。
常见字符集有: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特点:
繁体中文字符集&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和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。
对于是否乱码的判断关键在于:判断当前编码解码方式是否一致以及该字符集是否兼容该文字(比如中文)。也就是说,乱码的出现是因为编码和解码时用了不同或者不兼容的字符集。比如说一个用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,使用操作系统的默认语言编码来解释。
import sys
print sys.getdefaultencoding() #输出结果为ascii #或者
reload(sys)
sys.setdefaultenconding("utf-8") #修改系统默认编码为utf-8
print sys.getdefaultencoding() #如果没有修改过默认编码,输出结果为utf-8
# -*- coding: utf-8 -*-
#或者
#coding=utf-8 #若是Windows下自带BOM且想去掉BOM字符,可修改encoding为utf-8_sig / utf_8_sig
>>>import chardet #需要自行安装chardet编码检测包 pip install detect (Anaconda需要在其对应的Scripts目录下打开命令行)
>>>str = "string"
>>>chardet.detect(str2)
{'confidence': 1.0, 'language': '', 'encoding': 'ascii'}
#英文
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, 使用对的处理方法(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)。
尾言:文中若有笔误或不正确的地方,烦请包涵并指出;若有其他想法或意见也欢迎与我([email protected])交流,谢谢。