在看了很多的博客文章之后,总结整理得到了以下文章,非常感谢这些无私奉献的博主!
文章末尾有本文引用的文章的链接,如果有漏掉的文章引用,可以发邮件联系我,随后再次附上链接!
侵删!!!
这一部分是上篇,主要讲的是字符、字符集和字符编码的一些概念,以及他们在python中的一些简单的代码示例,偏向于概念。
下篇会说编码和解码部分,以及在python中会遇到的一些编码问题,偏向于实际应用一点。
这绝对是个源远流长的大坑,对于新手来说恶心致死(尤其是windows)...........
一、字符、字符集、字符编码
1.1 重 要 概 念
字符 : 字符是各种文字和符号的总称,包括各个国家文字、标点符号、图形符号、数字等;
字符集 : 字符集是多个字符的集合,字符集种类较多,每个字符集包含的字符个数不同,常见字符集有:ASCII字符集、ISO 8859字符集、GB2312字符集、BIG5字符集、GB18030字符集、Unicode字符集等;
字符编码:
1、 计算机要准确的处理各种字符集文字,需要进行字符编码,以便计算机能够识别和存储各种文字。
2、 字符编码(encoding)和字符集不同。字符集只是字符的集合,不一定适合作网络传送、处理,有时须经编码(encode)后才能应用。如Unicode可依不同需要以UTF-8、UTF-16、UTF-32等方式编码。
3、字符编码就是以二进制的数字来对应字符集的字符。 因此,对字符进行编码,是信息交流的技术基础。
字节和位: 字节byte和位bit是电脑里的数据量单位
1、1个字节等于8位,1byte=8bit ;
2、1bit在磁盘中以二进制0 1的形式保存,凸起的地方代表数字1,凹的地方代表数字0;
概括:
1、规定每个"字符"分别用一个字节还是多个字节存储,用哪些字节来存储,这个规定就叫做"编码"。
2、各个国家和地区在制定编码标准的时候,"字符的集合"和"编码"一般都是同时制定的。因此,平常我们所说的"字符集",比如:GB2312, GBK, JIS 等,除了有"字符的集合"这层含义外,同时也包含了"编码"的含义。
3、注意:Unicode字符集有多种编码方式,如UTF-8、UTF-16等;ASCII只有一种编码方式;大多数MBCS(多字节符号系统)(包括GB2312,GBK)也只有一种编码方式。
一个有趣的例子:
1、在显示器上看见的文字、图片等信息在电脑里面,其实并不是我们看见的样子,即使你知道所有信息都存储在硬盘里,把它拆开也看不见里面有任何东西,只有些盘片。假设,你用显微镜把盘片放大,会看见盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化;凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。
2、那么字母"A"在硬盘上是如何存储的呢?可能小张计算机存储字母"A"是1100001,而小王存储字母"A"是11000010,这样双方交换信息时就会误解。比如小张把1100001发送给小王,小王并不认为1100001是字母"A",可能认为这是字母"X",于是小王在用记事本访问存储在硬盘上的1100001时,在屏幕上显示的就是字母"X"。也就是说,小张和小王使用了不同的编码表。小张用的编码表是ASCII,ASCII编码表把26个字母都一一的对应到2进制1和0上;小王用的编码表可能是EBCDIC,只不过EBCDIC编码与ASCII编码中的字母和01的对应关系不同。一般地说,开放的操作系统(LINUX 、WINDOWS等)采用ASCII 编码,而大型主机系统(MVS 、OS/390等)采用EBCDIC 编码。在发送数据给对方前,需要事先告知对方自己所使用的编码,或者通过转码,使不同编码方案的两个系统可沟通自如。
这个例子说明了三点:
1、不管是任何文字图片等,最后都会以二进制的形式储存到电脑的磁盘中(比如记事本A.txt,内容为"ABC"文件,在此磁盘中表现的就是01 01这种二进制形式) 盘片表面凹凸不平,凸起的地方被磁化,凹的地方是没有被磁化,凸起的地方代表数字1,凹的地方代表数字0。硬盘只能用0和1来表示所有文字、图片等信息。
2、 任何文件要储存到电脑中,都会事先进行编码,然后储存到电脑的磁盘中,比如A.txt文件,默认编码为ANSI编码,也可以编码为UTF-8,然而不同的编码方式 对应着计算机用一个字节还是多个字节存储,用哪些字节来存储。
3、在双方数据进行通讯时,要么就保证发送方和接受方的数据编码是相同,要么就是其中一方需要转码
1.2 各 类 编 码
ASCII:
渊源:开始计算机只在美国使用;
规定:每个字节有0、1两个状态,八位的字节一共可以组合出256 (28) 种不同的状态。其中的编号为0~32的状态分别规定了特殊的用途(比如说:一但终端、打印机遇上约定好的这些字节被传过来时,就要做一些约定的动作。遇上0×10(0x开头代表十六进制), 终端就换行)。这些0×20(32个)以下的字节状态称为"控制码"。所有的空格、标点符号、数字、大小写字母分别用连续的字节状态表示,一直编到了第127号,这样计算机就可以用不同字节来存储英语的文字了;
结果:这个方案叫做"ASCII"编码(American Standard Code for Information Interchange,美国信息互换标准代码)。当时世界上所有的计算机都用同样的ASCII方案来保存英文文字。
以下是ASCII表:
python中ACSII编码示例:
- print
‘\x71‘ - q
- print
‘\x71‘.decode(‘ascii‘) - q
- print
‘\x71‘.decode(‘gb2312‘) - q
- print
‘\x71‘.decode(‘gbk‘) - q
- print
‘\x71‘.decode(‘utf-8‘) - q
tips:注意区别‘\x6d‘,0x6d,‘0x6d‘
‘\x6d‘:编码为十六进制6d的字符
0x6d:十六进制6d
‘0x6d‘:字符串0x6d
扩展字符集:
渊源:世界各地的都开始使用计算机,但是很多国家用的不是英文,他们的字母里有许多是ASCII里没有的;
编码规定:采用 127号之后的空位来表示这些新的字母、符号,一直把序号编到了最后一个状态255。从128 到255这一页的字符集被称"扩展字符集"。
GB2312:
渊源:等中国人们得到计算机时,已经没有可以利用的字节状态来表示汉字,况且有6000多个常用汉字需要保存。
编码规定:把那些127号之后的奇异符号们直接取消掉。规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1(161)用到 0xF7(247),后面一个字节(低字节)从0xA1到0xFE(254)。
结果:这样我们就可以组合出大约7000多个简体汉字了。在这些编码里,我们还把数学符号、罗马希腊的 字母、日文的假名们都编进去了,连在 ASCII 里本来就有的数字、标点、字母都统统重新编了两个字节长的编码,这就是常说的"全角"字符,而原来在127号以下的那些就叫"半角"字符了。
总结:这种汉字方案叫做 "GB2312"。GB2312 是对 ASCII 的中文扩展,兼容ASCII。
GB2312编码的详细说明可参考:http://www.qqxiuzi.cn/zh/hanzi-gb2312-bianma.php
python中GB2312示例:
- print
‘\xb0\xa1‘.decode(‘gb2312‘) - 啊
- print
‘\xb0\xa1‘.decode(‘gbk‘) - 啊
- print
‘\xb0\xa1‘.decode(‘ascii‘) - UnicodeDecodeError:‘ascii‘ codec can‘t decode byte 0xb0 in position 0: ordinal not in range(128)
GBK:
渊源:中国的汉字太多了,很多汉字还不能用GB2312编码表示。于是继续把 GB2312 没有用到的码位用上 ,还是不够用;
编码规定:不再要求低字节一定是127号之后的内码,只要第一个字节是大于127就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。
结果:扩展之后的编码方案被称为GBK标准,GBK包括了GB2312 的所有内容,同时又增加了近20000个新的汉字(包括繁体字)和符号。 GBK是GB2312的扩展。
GBK编码的详细说明可参考:http://www.qqxiuzi.cn/zh/hanzi-gbk-bianma.php
python中GBK示例:
- print
‘\xaa\x40‘.decode(‘gbk‘) - 狜
- print
‘\xaa\x40‘.decode(‘gb2312‘) - UnicodeDecodeError:
‘gb2312‘ codec can‘t decode bytes in position 0-1: illegal ultibyte sequence
GB18030:少数民族也要用电脑,于是再扩展了几千个新的少数民族的字,称为GB18030。GB18030是GBK的扩展。
DBCS : GBK和GB18030统称为DBCS(Double Byte Charecter Set 双字节字符集)。在DBCS系列标准里,最大的特点是两字节长的汉字字符和一字节长的英文字符并存于同一套编码方案里,因此他们写的程序为了支持中文处理,必须要注意字串里的每一个字节的值,如果这个值是大于127的,那么就认为一个双字节字符集里的字符出现了。一个汉字算两个英文字符。
UNICODE:
渊源:当时每个国家都有一套自己的编码标准,结果互相之间谁也不懂谁的编码,谁也不支持别人的编码。而且还有那些一时用不上电脑的穷苦民族,他们的文字又怎么办? 此时,ISO (国际标准化组织)决定解决这个问题。
编码规定:废了所有的地区性编码方案,重新搞一个包括了地球上所有文化、所有字母和符号的编码。他们打算叫它"Universal Multiple-Octet Coded Character Set",简称 UCS, 俗称 "unicode"。
unicode开始制订时,计算机的存储器容量极大地发展了,空间再也不成为问题了。于是 ISO 就直接规定必须用两个字节,也就是16位来统一表示所有的字符,对于ASCII里的那些"半角"字符,unicode包持其原编码不变,只是将其长度由原来的8位扩展为16位,而其他文化和语言的字符则全部重新统一编码。由于"半角"英文符号只需要用到低8位(其高8位永远是0),因此这种方案在保存英文文本时会多浪费一倍的空间。
这时候,一个汉字不再是相当于两个字符了,而是一个!是的,从unicode开始,无论是半角的英文字母,还是全角的汉字,它们都是统一的"一个字符"!同时,也都是统一的"两个字节",请注意"字符"和"字节"两个术语的不同,"字节"是一个8位的物理存贮单元,而"字符"则是一个文化相关的符号。在unicode中,一个字符就是两个字节。一个汉字算两个英文字符的时代已经快过去了。
unicode同样也不完美,这里就有两个的问题,一个是:如何才能区别unicode和ascii?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储空间来说是极大的浪费,文本文件的大小会因此大出二三倍,这是难以接受的。
它们造成的结果是:1)出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。
2)Unicode在很长一段时间内无法推广,直到互联网的出现。
UTF-8 : 互联网的普及,强烈要求出现一种统一的编码方式。为解决unicode如何在网络上传输的问题,于是众多UTF(UCS Transfer Format)标准出现了。UTF-8就是在互联网上使用最广的一种Unicode的实现方式(其他实现方式还包括UTF-16(字符用两个字节或四个字节表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用)。顾名思义,UTF-8就是每次8个位传输数据,而UTF-16就是每次16个位。UTF-8是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。注意了,UTF-8是Unicode的实现方式之一!
UTF-8最大的一个特点 : 它是一种变长的编码方式。UTF-8使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。当字符在ASCII 码的范围时,就用一个字节表示,保留了ASCII字符一个字节的编码做为它的一部分,注意的是UNICODE一个中文字符占2个字节,而UTF-8一个中文字符占3个字节。从unicode到uft-8并不是直接的对应,而是要过一些算法和规则来转换。
UTF-8的编码规则(与unicode之间的转换):
UTF-8的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
2)对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
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
跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
已知"严"的unicode是4E25(100111000100101),根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此"严"的UTF-8编码需要三个字节,即格式是"1110xxxx 10xxxxxx 10xxxxxx"。然后,从"严"的最后一个二进制位开始,依次从后向前填入格式中的x,多出的位补0。这样就得到了,"严"的UTF-8编码是"11100100 10111000 10100101",转换成十六进制就是E4B8A5。
python中UTF-8,UNICODE示例:
- u‘严‘
- u‘\u4e25‘
#unicode - u‘严‘.encode(‘utf-8‘)
- ‘\xe4\xb8\xa5‘
#utf-8
一切都是为了节省你的硬盘和流量!!!
Little endian和Big endian:Unicode码可以采用UCS-2格式直接存储。以汉字"严"为例,Unicode码是4E25,需要用两个字节存储,一个字节是4E,另一个字节是25。存储的时候,4E在前,25在后,就是Big endian方式;25在前,4E在后,就是Little endian方式。
这两个古怪的名称来自英国作家斯威夫特的《格列佛游记》。在该书中,小人国里爆发了内战,战争起因是人们争论,吃鸡蛋时究竟是从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。为了这件事情,前后爆发了六次战争,一个皇帝送了命,另一个皇帝丢了王位。因此,第一个字节在前,就是"大头方式"(Big endian),第二个字节在前就是"小头方式"(Little endian)。
那么很自然的,就会出现一个问题:计算机怎么知道某一个文件到底采用哪一种方式编码?
Unicode规范中定义,每一个文件的最前面分别加入一个表示编码顺序的字符,这个字符的名字叫做"零宽度非换行空格"(ZERO WIDTH NO-BREAK SPACE),用FEFF表示。这正好是两个字节,而且FF比FE大1。
如果一个文本文件的头两个字节是FE FF,就表示该文件采用大头方式;如果头两个字节是FF FE,就表示该文件采用小头方式。
实例:
打开"记事本"程序Notepad.exe,新建一个文本文件,内容就是一个"严"字,依次采用ANSI,Unicode,Unicode big endian 和 UTF-8编码方式保存。然后,用文本编辑软件UltraEdit中的"十六进制功能",观察该文件的内部编码方式。
1)ANSI:文件的编码就是两个字节"D1 CF",这正是"严"的GB2312编码,这也暗示GB2312是采用大头方式存储的。
2)Unicode:编码是四个字节"FF FE 25 4E",其中"FF FE"表明是小头方式存储,真正的编码是4E25。(FF FE)
3)Unicode big endian:编码是四个字节"FE FF 4E 25",其中"FE FF"表明是大头方式存储。(FE FF)
4)UTF-8:编码是六个字节"EF BB BF E4 B8 A5",前三个字节"EF BB BF"表示这是UTF-8编码,后三个"E4B8A5"就是"严"的具体编码,它的存储顺序与编码顺序是一致的。(EF BB BF)
其他:
在Windows的Notepad.exe中, 保存文件的格式可以看到有如下几种:
不是说Unicode只是字符集吗, 为什么上面显示可以保存为Unicode"编码"? 好吧, 其实这是Windows在命名上一个操蛋的
地方. 因为Windows内部使用UTF-16小端(UTF-16LE)作为默认编码,并且认为这就是Unicode的标准编码格式. 在Windows的世界中,
存在着ANSI字符串(在当前系
统代码页中, 不可拓展),以及Unicode字符串(内部以UTF16-LE编码保存). 因此notepad里所说的
Unicode大端,其实就是UTF16-BE.
这其实也不怪Windows, 因为这是在Unicode出现的早期设计的, 那时我们还没意识到UCS-2的不足, 而且UTF-8还没有被发明出来.
这也是为什么Windows对UTF8的支持如此之差的原因之一吧.
参考资料和博客:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://blog.chinaunix.net/uid-200142-id-4461708.html
http://www.cnblogs.com/evening/archive/2012/04/19/2457440.html
http://blog.csdn.net/olanlanxiari/article/details/48201231
http://www.jb51.net/article/87739.htm
http://www.cnblogs.com/JohnABC/p/4015504.html
http://www.cnblogs.com/work115/p/5924446.html
https://blog.ernest.me/post/python-setdefaultencoding-unicode-bytes