如果经常写 python2, 肯定会遇到各种 "奇怪" 的字符编码问题, 每次都通过谷歌解决了, 但是为什么会造成这种乱码, decode/encode 失败等等, 本文就字符和字符编码做一个总结, 更加清晰区分诸多的编码.
字符集
一个系统支持的所有抽象字符的集合. 字符是文字和符号的总称, 包含各个国家文字, 标点符号, 图像符号, 数字等. 它为每一个字符分配一个唯一的 ID, 一般称之为码位, 码点.
字符编码
它是一套规则, 使用该规则能够将自然语言的字符的一个集合与其他东西的一个集合进行配对, 在符号集合和数字系统中建立映射联系. 在计算机中, 处理信息是利用元件不同状态组合来存储和处理信息的, 因此, 字符编码就是将符号转化为计算机可以接受的数字系统的数, 称为数字代码. 它将上面字符集的码位转化为字节序列的规则, 此过程称之为编码, 解码.
常见字符集
ASCII 字符集, GB2312 字符集, Unicode 字符集等. 计算机需要准确处理各种字符集文字, 需要进行字符编码, 以便能够识别和存储各种文字.
常见字符编码
UTF 编码
ASCII 码
在计算机内部, 所有信息最终都是一个二进制值. 每一个二进制位(bit), 有 0 和 1 两种状态, 因此, 8 个二进制位可以组合出 256 种状态, 这被称为字节(byte). 上个世纪 60 年代, 美国制定了一套字符编码, 对英文字符与二进制之间做了联系, 这被称为 ASCII 码, 一直沿用至今.
ASCII 码一共规定了 128 个字符, 比如 SPACE 是 32,A 是 65, 这 128 个符号只咱用了一个字节的后面七位, 最前面的一位统一规定为 0.
非 ASCII 码
英语用 128 个符号编码足够了, 但是用来表示其他语言显然是不够的, 于是, 欧洲有些国家利用字节中闲置的最高位编入了新的符号, 这样一来, 欧洲国家使用的编码体系, 可以最多表示 256 个字符.
但是到了亚洲国家, 使用的符号更多了, 光汉字就 10 万多个, 一个字节只能表示 256 种符号, 肯定是不够的, 就必须使用多个字节表达一个符号. 比如常见的 GB2312 编码, 使用两个字节表示一个汉字, 所以理论上最多可以表示 256*256=65536 个符号.
GBK 码
GBK 编码是对 GB2312 的扩展, 完全兼容 GB2312. 采用双字节编码方案, 剔出 xx7F 码位, 共 23940 个码位, 共收录汉字和图形符号 21886 个, GBK 编码方案于 1995 年 12 月 15 日发布. 它几乎完美支持汉字, 因此经常会遇见 GBK 与 Unicode 的转换.
Unicode 码
如上文所述, 世界上有多种编码方法, 同一个二进制数字可以被解释称不同的符号. 因此, 在打开一个文本文件时候, 就必须知道它的编码方式, 用错误的编码方式打开, 就会出现乱码.
假如, 有一种编码, 将世界上所有的符号都纳入其中, 每一种符号都给予独一无二的编码, 那么乱码问题就不会存在了. 因此, 产生了 Unicode 编码, 这是一种所有符号的编码.
Unicode 显然是一个巨大的集合, 现在的规模可以容纳 100 多万个符号. 每个符号的编码都不一样, 比如 U+0041 表示英语的大写字母 A,U+4e25 表示汉字严.
在 Unicode 庞大的字符集的优势下, 还存在一个问题, 比如一个汉字,"严" 的 Unicode 是十六进制 4e25, 转成二进制足足有 15 位, 也就是, 这个符号需要 2 个字节, 表示其他字符还存在 3 个字节或者更多. 计算机怎么区别三个字节表示的是同一个符号而不是分开表示三个呢? 如果 Unicode 统一规定, 每个符号用 3 个字节表示, 但是某些字母显然不需要 3 个, 那么就浪费了空间, 文本文件大小超出了很多, 这显然是不合理的. 直到 UTF8 字符编码出现了.
UTF8 字符编码
随着互联网的发展, 强烈要求出现一种统一的编码方式. UTF8 就是在互联网中使用最多的对 Unicode 的实现方式. 还有其他实现, 比如 UTF16(字符用 2 个字节或者 4 个字节表示),UTF32(字符用 4 个字节表示).UTF8 是 Unicode 的实现方式之一, 也是最为常见的实现方式.
UTF8 的最大特点是, 它是一种变长编码, 可以使用 1-4 个字节表示一个符号, 根据不同的符号来变化字节长度.
UTF8 编码规则只有两条:
1)对于单字节的符号, 字节的第一位设为 0, 后面的 7 位为这个符号的 Unicode 码. 因此, 对于英文字母, UTF8 编码和 ASCII 编码是相同的.
2)对于非单字节 (假设字节长度为 N) 的符号, 第一个字节的前 N 位都设为 1, 第 N+1 设为 0, 后面字节的前两位一律设为 10, 剩下的没有提及的二进制, 全部为这个符号的 Unicode 码.
下面总结下编码规则:
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
举个例子, 以汉字 "严" 为例, 它的 Unicode 是 4e25(100111000100101), 对应上表, 处于第三行, 因此严的 UTF8 编码需要 3 个字节. 然后, 从严的最后一个二进制位开始, 从后向前填入 X, 多出的位补 0, 就可以计算出 11100100 10111000 10100101, 转成十六进制就是 E4B8A5.
Unicode 与 UTF8 之间转换
严的 Unicode 编码是 4e25,UTF8 是 E4B8A5, 两者是不一样的, 可以通过程序实现转码.
在 Windows 下有记事本小程序 notepad.exe, 打开文件后可以通过另存为, 选择编码格式, 重新保存新的文本文件. 支持 ANSI,Unicode,Unicde big endlian 和 UTF8.
1)ANSI 是记事本默认编码方式, 对于简体中文是 GB2312. 正是因为这个, Python 读取文本文件时候一定要小心它的编码类型, 因为不能直观到文本文件的格式.
2)Unicode 编码这里使用 UCS-2 编码方式, 采用两个字节存入字符的 Unicode 编码.
Python 编码为什么如此蛋疼
总结了几种编码和编码规则, 但是在 Python2 中编码问题就像幽灵一样困扰着开发者, 其根本解决办法是理解清楚 python2 在内存中的字符存在的编码格式, 在程序代码中始终采用 Unicode 编码处理, 只有在输出时候才做 encode 处理. 核心思想是: 保证 Python 运行过程中字符编码格式是 unicode 编码, 在任何地方. 关于 Python 乱码问题, 会在专门文章做分析, 这里提供一个链接供参考.
编码探测
使用 chardet 可以很方便的实现字符串 / 文件的编码检测. 尤其是中文网页, 有的页面使用 GBK/GB2312, 有的使用 UTF8, 使用 chardet 基本可以探测出编码格式.
来源: http://www.bubuko.com/infodetail-2696529.html