转自 :http://blog.csdn.net/gqtcgq/article/details/47068817
一:字符编码简介 1:ASCII
最初的计算机的使用是在美国,所用到的字符也就是现在键盘上的一些符号和少数儿个特殊的符号,一个字节所就能足以容纳所有的这些字符,实际上表示这些字符的字节最高位都为 0,也就是说这些字节都在 0 到 127 之间,如字符 a 对应数字 97.这套编码规则被称为 ASCII(美国标准信息交换码).
2:GBK,GB2312
随着计算机的应用和普及,许多国家都把本地的字符集引入了计算机,大大扩展了计算机中字符的范围.以中文为例,一个字节是不能容纳所有的中文汉字的,因此大陆将每一个中文字符都用两个字节的数字来表示,原有的 ASCII 字符的编码保持不变,仍用一个字节表示,为了将一个中文字符与两个 ASCII 码字符相区别,中文字符的每个字节的最高位都为 1,这套编码规则称为 GBK(国标码),后来,又在 GBK 的基础上对更多的中文字符 (包括繁体) 进行了编码,新的编码系统就是 GB2312,可见 GBK 是 GB2312 的子集.
3:Unicode
每个国家和地区都制定了一套自己的编码,那么同样的一个字节,在不同的国家和地区就代表了不同的字符.比如,130 在法语编码中代表了 é,在希伯来语编码中却代表了字母 Gimel (?),在俄语编码中又会代表另一个符号.
为了解决各个国家和地区使用本地化字符编码带来的不利影响,将全世界所有的符号进行了统一编码,称之为 Unicode 编码.这是一种所有符号的编码.如 "中" 这个符号,在全世界的任何角落始终对应的都是一个十六进制的数字 4e2d.
最初的 Unicode 标准 UCS-2 使用两个字节表示一个字符,所以你常常可以听到 Unicode 使用两个字节表示一个字符的说法.但过了不久有人觉得 256*256 太少了,还是不够用,于是出现了 UCS-4 标准,它使用 4 个字节表示一个字符,不过我们用的最多的仍然是 UCS-2.
4:UTF
注意,UCS(Unicode Character Set) 还仅仅是字符对应码位的一张表而已,比如 "中" 这个字的码位是 4E2D.字符具体如何传输和储存则是由 UTF(UCS Transformation Format) 来负责.
一开始这事很简单,直接使用 UCS 的码位来保存,这就是 UTF-16,比如,"汉" 直接使用 \ x4E\x2D 保存 (UTF-16-BE),或是倒过来使用 \ x2D\x4E 保存 (UTF-16-LE).
这里就有两个严重的问题,第一个问题是,如何才能区别 Unicode 和 ASCII?计算机怎么知道两个字节表示一个符号,而不是分别表示两个符号呢?第二个问题是,英文字母只用一个字节表示就够了,如果 Unicode 统一规定,每个符号用两个字节表示,这对于存储来说是极大的浪费.于是 UTF-8 横空出世.
5:UTF-8
UTF-8 是使用最广的一种 Unicode 的实现方式.其他实现方式还包括 UTF-16(字符用两个字节或四个字节表示)和 UTF-32(字符用四个字节表示),不过在互联网上基本不用.重复一遍,这里的关系是,UTF-8 是 Unicode 的实现方式之一.
UTF-8 最大的一个特点,就是它是一种变长的编码方式.它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度.UTF-8 的编码规则很简单,只有二条:
1)对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 unicode 码.因此对于英语字母,UTF-8 编码和 ASCII 码是相同的.
2)对于 n 字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10.剩下的没有提及的二进制位,全部为这个符号的 unicode 码.
下表总结了编码规则,字母 x 表示可用编码的位.
Unicode 符号范围
UTF-8 编码方式
跟据上表,解读 UTF-8 编码非常简单.如果一个字节的第一位是 0,则这个字节单独就是一个字符;如果第一位是 1,则连续有多少个 1,就表示当前字符占用多少个字节.
00 00 00 00 - 00 00 00 7F
0xxxxxxx
00 00 00 80 - 00 00 07 FF
110xxxxx 10xxxxxx
00 00 08 00 - 00 00 FF FF
1110xxxx 10xxxxxx 10xxxxxx
00 01 00 00 - 00 10 FF FF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
下面以汉字 "严" 为例,演示如何实现 UTF-8 编码.
已知 "严" 的 unicode 是 4E25(1001110 00100101),根据上表,可以发现 4E25 处在第三行的范围内(00000800-0000 FFFF),因此 "严" 的 UTF-8 编码需要三个字节,即格式是 "1110xxxx 10xxxxxx 10xxxxxx".然后,从 "严" 的最后一个二进制位开始,依次从后向前填入格式中的 x,多出的位补 0.这样就得到了,"严" 的 UTF-8 编码是 "11100100 10111000 10100101",转换成十六进制就是 E4B8A5.
6:BOM
BOM(Byte Order Mark).UTF 引入了 BOM 来表示自身编码,如果一开始读入的几个字节是其中之一,则代表接下来要读取的文字使用的编码是相应的编码:
如果一个文本文件的头两个字节是 FE FF,就表示该文件采用大头方式;如果头两个字节是 FF FE,就表示该文件采用小头方式.以汉字 "严" 为例,Unicode 码是 4E25,需要用两个字节存储,一个字节是 4E,另一个字节是 25.存储的时候,4E 在前,25 在后,就是 Big endian 方式;25 在前,4E 在后,就是 Little endian 方式.
BOM_UTF8 '\xef\xbb\xbf'
BOM_UTF16_LE '\xff\xfe'
BOM_UTF16_BE '\xfe\xff'
一般情况下,不建议在 Linux 中使用 BOM.
7:Unicode 与 UTF-8 之间的转换
"严" 的 Unicode 码是 4E25,UTF-8 编码是 E4B8A5,以该汉字为例,利用 Ultraedit 查看各种编码的具体值,一般的编辑器都支持以不同的编码方式保存文本,编码方式如下:
1)ANSI 是默认的编码方式.对于英文文件是 ASCII 编码,对于简体中文文件是 GB2312 编码(只针对 Windows 简体中文版,如果是繁体中文版会采用 Big5 码).这种方式编码的文件,就是两个字节 "D1 CF",这正是 "严" 的 GB2312 编码:
2)UTF16 编码指的是 UCS-2 编码方式,即直接用两个字节存入字符的 Unicode 码.编码是四个字节 "FF FE 25 4E",其中 "FF FE" 表明是小头方式存储:
3)UTF16-NOBOM 编码与上一个选项相对应,只不过没有 BOM 编码,也就是文件中只存储 UTF16 编码 "25 4E":
4)UTF-8 编码,文件中共 6 个字节:"EF BB BF E4 B8 A5",其中前三个为 BOM 编码,后三个为 UTF8 编码:
6)UTF8-NOBOM 编码与上一个选项相对应,只不过没有 BOM 编码,也就是文件中只存储 UTF8 编码 "E4 B8 A5":
二:Python 代码文件的编码
Python 解释器会使用某种编码方式来解释 Python 源代码文件,默认情况下,这种编码方式就是 ASCII.
Python2.1 版本,在 Python 源码文件中,只能以以基于 Latin-1 的 "转义 unicode" 的方式来书写 Unicode 字符,这对于亚洲的程序员是很不友好的.解决该问题的方法是,在源码文件的顶部,使用某种特殊的注释方式来表明源码文件的编码.
为了表明源码文件的编码,这种特殊的注释必须位于源码文件的第一行或第二行,类似于:
或:
[python] view plain copy
#coding=<encodingname>
或:
[python] view plain copy
#!/usr/bin/python
# -*- coding:<encoding name> -*-
这种方式的本质是:文件的第一行或第二行必须能匹配正则表达式:
[python] view plain copy
#!/usr/bin/python
# vim: setfileencoding=<encoding name> :
"coding[:=]\s*([-\w.]+)",该表达式中的 group1 就会被解释为编码名称,如果 Python 无法识别该编码,则在编译时就会报错.
像 Windows 这样的平台,会在 Unicode 文件的最开始加上 BOM 字节码,UTF-8 文件的字节码是:"xef\xbb\xbf".为了兼容这种方式,包含这种字节码的文件,即使没有字节编码注释,也会被解释为 "utf-8".如果一个源码文件,既有编码注释,又有 UTF-8 的 BOM 字节码,则在编码注释中的编码名称只能是 "utf-8"("utf8" 都不行),否则会报错:
"SyntaxError: encodingproblem: utf-8".
下面是一些使用编码注释的例子:
1. Emacs 风格的文件编码注释:
2. 使用纯文本方式的注释:
[python] view plain copy
#!/usr/bin/python
# -*- coding: latin-1 -*-
import os, sys
...
[python] view plain copy
#!/usr/bin/python
# -*- coding: iso-8859-15 -*-
import os, sys
...
[python] view plain copy
#!/usr/bin/python
# -*- coding: ascii -*-
import os, sys
...
3. 没有编码注释,则 Python 解释器默认使用 ASCII.
[python] view plain copy
# This Python file uses the following encoding:utf-8
import os, sys
...
如果没声明编码,但是文件中又包含非 ASCII 编码的字符的话,python 解析器去解析的 python 文件,就会报错.
4. 下面这些语法不起作用
[python] view plain copy
#!/usr/local/bin/python
import os, sys
...
没有加 "coding:" 前缀:
编码方式不在第一行或第二行:
[python] view plain copy
#!/usr/local/bin/python
# latin-1
import os, sys
...
不支持的编码方式:
[python] view plain copy
#!/usr/local/bin/python
#
# -*- coding: latin-1 -*-
import os, sys
...
5:注意:
[python] view plain copy
#!/usr/local/bin/python
# -*- coding: utf-42 -*-
import os, sys
...
允许的编码方式包括 ASCII 兼容编码以及某些多字节编码,比如 SHIFT_JIS.但不包括为所有字符都是有双字节或者更多字节的编码,比如 UTF-16(注:也就是通常说的 Unicode,但 SHIFT_JIS 也好,GBK 也好,因为兼容 ASCII 编码,所以都可以在 Python 源文件里使用).
如果声明的编码与实际不符(就是说,文件实际上是以另外的编码保存的),出错的可能性很大.
文件的编码格式决定了在该源文件中声明的字符串的编码,str = '哈哈'
print repr(str)
a. 如果文件格式为 utf-8,则 str 的值为:'\xe5\x93\x88\xe5\x93\x88'(哈哈的 utf-8 编码)
b. 如果文件格式为 gbk,则 str 的值为:'\xb9\xfe\xb9\xfe'(哈哈的 gbk 编码)
三:str 和 unicode
str 和 unicode 都是 basestring 的子类.编码是指 unicode-->str,解码是指 str-->unicode.
str 是一个字节数组,这个字节数组表示的是对 unicode 对象编码 (可以是 utf-8,gbk,cp936,GB2312) 后的存储的格式.这里它仅仅是一个字节流,没有其它的含义,如果你想使这个字节流显示的内容有意义,就必须用正确的编码格式,解码显示. 对 UTF-8 编码的 str'哈哈'使用 len()函数时,结果是 6,因为实际上,UTF-8 编码的'哈哈'=='\x e5\x93\x88\xe5\x93\x88'.
unicode 才是真正意义上的字符串,对字节串 str 使用正确的字符编码进行解码后获得,例如'哈哈'的 unicode 对象为 u'\u54c8\u54c8' ,len(u" 哈哈 ") == 2
字符串在 Python 内部的表示是 unicode 编码,因此,在做编码转换时,通常需要以 unicode 作为中间编码,即先将其他编码的字符串解码(decode)成 unicode,再从 unicode 编码(encode)成另一种编码.
decode 的作用是将其他编码的字符串转换成 unicode 编码,如 str1.decode('gb2312'),表示将 gb2312 编码的字符串 str1 转换成 unicode 编码.
encode 的作用是将 unicode 编码转换成其他编码的字符串,如 str2.encode('gb2312'),表示将 unicode 编码的字符串 str2 转换成 gb2312 编码.
因此,转码的时候一定要先搞明白,字符串 str 是什么编码,然后 decode 成 unicode,然后再 encode 成其他编码
str.decode([encoding[, errors]])
使用 encoding 指示的编码,对 str 进行解码,返回一个 unicode 对象.默认情况下 encoding 是 "字符串默认编码",比如 ascii.
errors 指示如何处理解码错误,默认情况下是 "strict",也就是遇到解码错误时,直接抛出 UnicodeError 异常.其他的 errors 值可以有 "ignore","replace" 等.
unicode(object[, encoding[, errors]])
与 str.decode 作用相同,但是该方法要快一些:http://stackoverflow.com/questions/440320/unicode-vs-str-decode-for-a-utf8-encoded-byte-string-python-2-x
str.encode([encoding[, errors]])
返回一个经 encoding 编码后的 str 对象,默认的 encoding 是 "默认字符串编码".
errors 指示如何处理解码错误,默认情况下是 "strict",也就是遇到解码错误时,直接抛出 UnicodeError 异常.其他的 errors 值可以有 "ignore","replace",'xmlcharrefreplace', 'backslashreplace'等.
一般情况下,对 Unicode 对象进行编码 encode(),返回 str 对象.对 str 对象调用解码 decode(),返回 unicode 对象.
但是对 str 调用 encode 也不会报错,str.encode() 实际上就等价于 str.decode(sys.defaultencoding).encode(). 而 sys.defaultencoding 一般是 ascii.
同样的,对 unicode 对象进行解码 unicode.decode 实际上就等价于 unicode.encode(sys.defaultencoding).decode()
四:示例
英文字符的 ASCII,UTF-8,GBK 等编码都是一样的,因此下面的示例仅讨论汉字的情况.并且保证源码文件的实际编码方式,和编码注释是一样的.
1:文件为默认编码(ASCII),无编码注释
运行文件,报错:SyntaxError: Non-ASCII character '\xb9' in file 2.py on line 2, butno encoding declared; see http://www.python.org/peps/pep-0263.htmlfor details
[python] view plain copy
astr ="哈哈"
原因:默认的文件编码为 ASCII,这种编码无法处理汉字,将文件保存为 GBK 或者 UTF-8 格式,并相应的注释声明.
2:文件编码为 UTF-8
运行文件,结果为:
[python] view plain copy
# -*- coding:UTF-8 -*-
def toHexString(s):
return ":".join("{0:x}".format(ord(c))for c in s)
ustr =u"哈哈"
print repr(ustr)
print ustr, "len is",len(ustr)
astr ="哈哈"
ustr =astr.decode("UTF-8")
print repr(ustr)
print ustr, "len is",len(ustr)
u'\u54c8\u54c8'
哈哈 len is 2
u'\u54c8\u54c8'
哈哈 len is 2
可见,上下两种方式,其实是等价的,在源码文件中直接使用 u"..." 编写 Unicode 字符串,使用文件的注释编码,将该 str 解码为 Unicode:
3:文件为 UTF-8 编码,
Python supports writing Unicode literals in anyencoding, but you have to declare the encoding being used. This is done byincluding a special comment as either the first or second line of the sourcefile.
(https://docs.python.org/2/howto/unicode.html#the-unicode-type)
运行文件,结果如下:
[python] view plain copy
# -*- coding:utf-8 -*-
astr ="哈哈"
print repr(astr)
print astr, "len is",len(astr)
ustr =astr.decode("gbk")
print repr(ustr)
print ustr, "len is",len(ustr)
'\xe5\x93\x88\xe5\x93\x88'
鍝堝搱 len is 6
(第一行输出:因为文件为 UTF-8 编码,所以输出 "哈哈" 的 UTF-8 编码)
(第二行输出:因为 print 语句它的实现是将要输出的内容传送给终端,终端会根据默认的编码(GBK)对输入的字节流进行解释,因为 '\xe5\x93\x88\xe5\x93\x88'用 GBK 去解释,其显示的出来就是 "鍝堝搱",而且,该 str 的长度就是编码的长度,为 6)
u'\u935d\u581d\u6431'
鍝堝搱 len is 3
(第一行输出:因为文件是 UTF-8 编码,但是却用 GBK 对 "哈哈",也就是'\xe5\x93\x88\xe5\x93\x88'进行解码,所以输出错误)
(第二行输出:Python 在向控制台输出 unicode 对象的时候会自动根据输出环境的编码(GBK)进行转换,而如果输出的不是 unicode 对象而是普通的 str 字符串,则会直接按照该字符串的编码输出字符串 (http://noalgo.info/578.html).所以,printustr 就等价于 print astr 了.如果将 ustr = astr.decode("gbk") 替换成 ustr =astr.decode("UTF-8"),则输出:
u'\u54c8\u54c8'
哈哈 len is 2
54c8:54c8
这是因为将 astr 按照文件编码注释声明的编码方式进行解码,解码成 Unicode 之后,print 时又自动进行 GBK 编码,所以会输出正确的字符)
4:打开具有汉字的文件:
文件名可以包含 Unicode 字符,操作系统在处理这样的文件时,将 Unicode 字符根据某种编码进行处理,比如 Max OS X 使用 UTF-8 编码,而 Windows 上的编码是可配置的,用 "mbcs" 来表示当前的编码.
sys.getfilesystemencoding() 函数可以得到当前文件系统使用的编码,但是其实可以不用管这些,当需要打开一个包含汉字的文件时,直接使用 Unicode 字符即可,这样它会自动转换为正确的编码字符串:
上面这三种方式,都可以正确打开该文件.
[python] view plain copy
# -*- coding:UTF-8 -*-
#astr = "哈哈. txt".decode("UTF-8")
#astr = u"哈哈. txt"
astr ="哈哈. txt".decode("UTF-8").encode("GBK")
try:
with open(astr) asfobj:
print fobj.read()
except Exception, e:
print "error is",e
参考:
http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html
http://www.cnblogs.com/huxi/articles/1897271.html https://www.python.org/dev/peps/pep-0263/ http://www.crifan.com/files/doc/docbook/char_encoding/release/html/char_encoding.html http://www.jb51.net/article/26543.htm
http://www.jb51.net/article/17560.htm
来源: http://www.bubuko.com/infodetail-2454004.html