More Related Content More from Xiaozhe Wang (6) 中文编码杂谈2. 文本编码的基本概念
字节(byte) - 最小粒度的可直接访问数据单元,通常为 8 bit
事实上 4、6、7、12 bit 等等宽度都有
八位元(octet) - 为避免产生理解歧义,将 8 bit 宽的数据单元称为 octet,正式的 RFC 规范和其他标准文档都用此术语
字符(character) - 语言的最小可独立存在的单元,为逻辑概念。一个字符可对应多种字型
字型(glyph) - 字符呈现的视觉形状:
a、ま、
字符集(character set) - 将一组文字字符实体收集在一起成为集合,称为一个字符集:
{a, b, c, ...} - English(Natural)
{は, ま, や, ...} - Japanese(Natural)
{, , , ...} - Klingon in StarTrek(Constructed)
{a, b, c, ..., は, ま, や, ..., , , , ...} - English、Japanese and Klingon(Mixed)
第2页 共36页 2011年07月20日 13:20
3. 文本编码的基本概念
编码字符集(coded character set) - 在字符集的基础上为将每个字符唯一映射到一段整数区间(码点空间,codepoint space)上,这样字符集和码点空间的整体称为一个编码字符
集
{a ⇔ 97, b ⇔ 98, c ⇔ 99, ...}
{は ⇔ 1233, ま ⇔ 1234, や ⇔ 1235, ...}
{ ⇔ 0xf8d0, ⇔ 0xf8d1, ⇔ 0xf8d2, ...}
码点空间中的每个可能位置称为码点或码位(code point)
已同唯一字符建立映射关系的码点称为已分配码位(assigned code point),反之为未分配码位(unassigned code point)
通常计算机行业所说的"字符集"意思都是指编码字符集(显然...)
第3页 共36页 2011年07月20日 13:20
4. 文本编码的基本概念
编码方案(encoding) - 为了解决字符集中整数编号在不同机器不同架构之间的内部表示差异,将码点对应的整数按照一套固定的方法规范转换为机器、架构相关的一串字节序列,这种
转换的方法规范和其面向的字符集被统称为编码方案
ASCII 是一种编码方案,其面向的字符集是最常用的英文字母、数字和符号(字符集码点空间包含 128 个码点,已全部分配)
ISO-8859-1(Latin1)是一种编码方案,其面向的字符集是在 ASCII 字符集上扩充了常见西欧语言字母(字符集码点空间包含 256 个码点,已全部分配)
UTF-8 是一种编码方案,其面向的字符集为 Unicode / UCS
第4页 共36页 2011年07月20日 13:20
5. 常用中文编码简介 - GB系列
GB2312 - 编码结果固定 2 字节
第一字节范围 0xA1~0xF7(10100001~11110111),其中 0xB0~0xF7 内为汉字
第二字节范围 0xA1~0xFE(10100001~11111110)
两个字节分别减去 0xA0(160)后的值传统上称为区码和位码
严格来说 GB2312 编码的字符集不包括 ASCII 字符集,但实际上二者互不重叠,且通常都是同时使用的
第5页 共36页 2011年07月20日 13:20
6. 常用中文编码简介 - GB系列
GBK - 编码结果 1 或 2 字节,兼容 GB2312
1 字节编码等同于 ASCII 编码,范围为 0x00~0x7F(00000000~01111111)
2 字节编码中:
第一字节范围 0x81~0xFE(10000000~11111110)
第二字节范围视第一字节取值为 0x40~0x7E 或 0x80~0xFE(后者为了兼容 GB2312)
第6页 共36页 2011年07月20日 13:20
7. 常用中文编码简介 - GB 系列
GB18030 - 1、2 或 4 字节,兼容 GB2312 和 GBK
1、2 字节形式等同于 GBK
4 字节形式涵盖了 CJK Unified Han Extension A 字符集,可视为两个双字节单元。双字节单元的第一字节范围为 0x81~0xFE,第二字节范围为 0x30~0x39(ASCII 字符
'0'~'9')。可用码点空间大小为 1260*1260=1,587,600
第7页 共36页 2011年07月20日 13:20
8. 常用中文编码简介 - Unicode 系列
UCS = Universal Character Set(统一字符集)
UCS-2 为 Unicode 2.0 标准前使用,用 16-bit 整数表达码点,故最多提供 65,535 个码点
UCS-4 为 Unicode 2.0 及其后标准使用,用 32-bit 整数表示码点,但规定码点上限为 0x10FFFF,故最多提供 1,114,111 个码点,已分配码点约 109,000 个
UCS-4 保留 UCS-2 的码点空间作为基本多语言平面(BMP,Basic Multilingual Plane),其余码点空间划分为若干增补多语言平面(Supplementary Multilingual Plane )
管理
习惯上以 U+UCS码点数值 的形式表示 UCS 中的特定字符
注意 Unicode 和 GB18030 并没有等价或包容关系,彼此都有对方无法表示的字符(集中在增补平面上),PUA(Private Use Area)更是无法互相对应!
第8页 共36页 2011年07月20日 13:20
9. 常用中文编码简介 - Unicode 系列
UTF = UCS Transformation Format(统一字符集转换格式)
是面向 UCS 字符集的编码方案,包括:
UTF-8 - 1~4 字节变长编码方案,兼容 ASCII 编码方案,可自同步纠错,使用最广泛
UTF-16 - 2、4 字节变长编码方案,不兼容 ASCII 编码方案,可自同步纠错,主要在 WinNT 和 Java 中应用
UTF-32 - 4 字节定长编码方案,空间利用率低,不可自同步纠错,极少使用
第9页 共36页 2011年07月20日 13:20
10. 常用中文编码简介 - Unicode 系列之 UTF-8
视码点所处范围选择对应长度的编码形式:
码点范围 MSB-LSB 编码结果 例子
U+0000~U+007F 0xxxxxxx 0xxxxxxx '$' → 24
U+0080~U+07FF 00000yyy yyxxxxxx 110yyyyy 10xxxxxx '¢' → C2 A2
第10页 共36页 2011年07月20日 13:20
11. 常用中文编码简介 - Unicode 系列之 UTF-8
码点范围 MSB-LSB 编码结果 例子
U+0800~U+FFFF zzzzyyyy yyxxxxxx 1110zzzz 10yyyyyy 10xxxxxx '€' → E2 82 AC
U+010000~U+10FFFF 000wwwzz zzzzyyyy yyxxxxxx 11110www 10zzzzzz 10yyyyyy 10xxxxxx '𐀀' → F0 90 80 80
第11页 共36页 2011年07月20日 13:20
12. 常用中文编码简介 - Unicode 系列之 UTF-16
UTF-16 基本码元为双字节形式,根据约定的字节序不同可分为 LE 和 BE 两种变体。编码方法:
U+0000~U+D7FF 和 U+E000~U+FFFF - 直接用一个 16 位码元表示
U+D800~U+DFFF - UTF-16 不能编码位于该区间的码点。为此 Unicode 标准将该区间永久保留,不会有任何字符分配在这里
U+10000~U+10FFFF - 用两个 16 位代理码元(surrogate pair)表示
第12页 共36页 2011年07月20日 13:20
13. 常用中文编码简介 - Unicode 系列之 UTF-16
代理码元计算方法:
将码点减去 0x10000,获得一个 20 位的整数 0~0xFFFFF
结果的最高 10 位(0~0x3FF 范围内的整数)加上 0xD800,作为第一码元(high surrogate)
最低 10 位加上 0xDC00,作为第二码元(low surrogate),落在 0xDC00~0xDFFF 区间内
码点 UTF-16LE 编码 UTF-16BE 编码
U+7F 0x7F 0x00 0x00 0x7F
U+10000 0x00 0xD8 0x00 0xDC 0xD8 0x00 0xDC 0x00
第13页 共36页 2011年07月20日 13:20
14. 挑战:编码混淆问题
GB2312 编码的 凉鞋 两个字的字节序列为:0xc1 0xb9 0xd0 0xac,转换为二进制为:11000001 10111001 11010000 10101100,前后两对字节恰好是 UTF-8 编码中有效的双字
节编码字符,转换为 Unicode 码点分别为 00001111001=121 和 10000101100=1068,对应 yЬ 两个字符,前一个字符 y 的编码形式符合 UTF-8 编码规则但不应使用(因 121 落在
0~127 的 ASCII 编码区间内,应使用单字节而非双字节编码),后一个字符 Ь 是西里尔字符(俄文)。另外还可以试试 芦苇……
第14页 共36页 2011年07月20日 13:20
15. 挑战:编码混淆问题
Unicode 标准要求 UTF-8 解码器明确拒绝如下无效的 UTF-8 编码字节序列:
规范中规定的无效字节:0xc0、0xc1 和 0xf5~0xff,前两者只可能用于进行 ASCII 字符的超长编码(overlong encoding),后者则可能用于编码超过当前 Unicode 规定最大码点
0x10FFFF 的数值
未预期的后续字节
没有跟上足够数量后续字节的起始字节
解码后数值的位数少于当前形式预期的位数,应该用更短的形式表示,这种情况称为超长编码(overlong encoding)
第15页 共36页 2011年07月20日 13:20
16. 挑战:编码混淆问题
php 的 mb_detect_encoding() 和 vim 都没有遵循这一要求,从而出现上述问题
即便满足要求还是会有混淆,根本原因是不同编码方案的定义范围存在重叠
杯具的结论:单纯对文本内容进行自动编码检测是个概率活,无法做到100%准确
第16页 共36页 2011年07月20日 13:20
17. 轻松一刻:"烫烫烫..."
Visual Studio 为了捕获非法内存访问问题,在调试模式下会将未显式初始化的内存块预先填充为特殊的数值:
未初始化的栈填充 0xCC(x86 的 INT3 指令,表示调试中断,这样若将该块数据作为代码指令运行就会直接中断程序并触发调试器) - 0xCC 0xCC 恰好是 GB2312 编码的烫,因此如果
输出字符串时访问越界,就是一串“烫烫烫烫...”(中文 Windows 默认使用 GBK/GB18030 编码)
使用 HeapAlloc()/LocalAlloc()/GlobalAlloc() 分配的未初始化空间填充 0xBAADF00D(即 Bad Food) - 小端系统上填充序列为 0x0D 0xF0 0xAD 0xBA,0xF0 0xAD 是
GB2312 的 瓠,so 调试时可能看到“.瓠..瓠..瓠.”
第17页 共36页 2011年07月20日 13:20
18. 轻松一刻:"烫烫烫..."
分配地址对齐的内存块前后会有哨兵块,其中填充 0xBD - 0xBD 0xBD 是 GB2312 的 浇,so 会看到”浇浇浇浇浇浇浇浇...“
已经被 new/malloc() 初始化但未被用户代码初始化的内存块填充 0xCD - 0xCD 0xCD 为 GB2312 的 屯,so……
已经被 delete/free() 释放的内存块会填充 0xDD 或 0xFEEEFEEE - 0xDD 0xDD 为 GB2312 的 葺,0xEE 0xFE 为 GB2312 的 铪,so……
第18页 共36页 2011年07月20日 13:20
19. 深入 MySQL 中的编码 - char 和 binary 的区别
char 单元是一个字符,varchar(255) 就是最多保存 255 个字符,若字段定义时设置编码为 utf8,则最大可占 255*4 个字节
binary 单元是一个字节(octet),varbinary(255) 就是最多保存 255 个字节,和具体编码没关系
第19页 共36页 2011年07月20日 13:20
20. 深入 MySQL 中的编码 - 字符序
字符序(collation) - 定义特定字符集上不同字符的大小和等价关系,用于确定排序、比较等操作的结果
以 _ci(即 case insensitive)结尾的字符序比较时忽略大小写差异,以 _cs (即 case sensitive)结尾的字符序比较时考虑大小写差异,二者还会考虑其他字符集相关的等价条件,如
是否区分声调符号(如 'Â' 和 'Ä')、是否区分多字符序列同单字符等价性(如在 latin1_german2_ci 字符序下比较 'Ö' 和 'OE' 或 utf8_unicode_ci 字符序下比较 'ß' 和 'ss')等等
以 _bin 结尾的字符序只是简单地比较字符编码后的字节串,不考虑任何等价条件。以 _bin 结尾的字符序比较符合中文使用者的习惯。
第20页 共36页 2011年07月20日 13:20
21. 深入 MySQL 中的编码 - 服务器变量
character_set_client - 客户端来源数据使用的字符集,客户端发出的 SQL 语句等文本数据在服务端首先会当作是该字符集编码的
character_set_connection - 连接层字符集,服务端会将收到的客户端数据统一转换为该字符集,所有未附加 introducer 的字面字符串都会被转换
character_set_database - 当前选中数据库的默认字符集,参与内部操作字符集的选择过程
character_set_results - 查询结果字符集
character_set_server - 默认的内部操作字符集,参与内部操作字符集的选择过程
SET NAMES charset 做了什么?
第21页 共36页 2011年07月20日 13:20
22. 深入 MySQL 中的编码 - 请求处理过程
MySQL 服务端收到请求时将请求数据从 character_set_client 转换为 character_set_connection
进行内部操作前将请求数据从 character_set_connection 转换为内部操作字符集,其确定方法如下:
使用每个数据字段的 CHARSET 设定值
若上述值不存在,则使用对应数据表的 CHARSET 设定值
若上述值不存在,则使用对应数据库的 CHARSET 设定值(即 character_set_database)
若上述值不存在,则使用 character_set_server 设定值
操作完成后将结果从内部操作字符集转换为 character_set_results 返回客户端
第22页 共36页 2011年07月20日 13:20
24. 深入 MySQL 中的编码 - 典型错误1
向默认字符集为 utf8 的数据表 table 的列 t 插入 utf8 编码的数据前没有设置连接字符集,查询时设置了 SET NAMES utf8
插入时根据MySQL服务器的默认设置,character_set_client、character_set_connection和character_set_results均为latin1
插入操作的数据将经过latin1=>latin1=>utf8的字符集转换过程,这一过程中每个插入的汉字都会从原始的3个字节变成6个字节保存
查询时的结果将经过utf8=>utf8的字符集转换过程,将保存的6个字节原封不动返回,产生乱码……
纠正方法:
UPDATE table SET t = CONVERT(BINARY(CONVERT(t USING latin1)) USING utf8)
第24页 共36页 2011年07月20日 13:20
26. 深入 MySQL 中的编码 - 典型错误2
向默认字符集为 latin1 的数据表 table 的列 t 插入 utf8 编码的数据前设置了 SET NAMES utf8
插入时根据连接字符集设置,character_set_client、character_set_connection和character_set_results均为utf8
插入数据将经过utf8=>utf8=>latin1的字符集转换,若原始数据中含有u0000~u00ff范围以外的Unicode字符,会因为无法在latin1字符集中表示而被转换为“?”(0x3F)符号,
以后查询时不管连接字符集设置如何都无法恢复其内容
第26页 共36页 2011年07月20日 13:20
28. 经验与技巧
有用的工具
od -tx1 / hd 直接以二进制方式查看文件内容
iconv 编码转换,强制转换时在目标编码后加上 TRANSLIT 将无法转换的字符替换为看起来相近的字符或 IGNORE 简单地忽略无法转换的字符
vim 中的技巧
普通模式下移动光标到字符上,g a 查看字符的 Unicode 码点,g 8 查看以 UTF-8 编码表示的字节序列
插入模式下 C-V u 4位16进制数,直接输入 BMP 内的 Unicode 字符;SMP 内的字符要用 U 8位16进制数形式输入
:digraphs 可查看特殊符号转义串,插入模式下 C-K {char1} {char2} 输入;或设置 set digraph 后,用 {char1} 退格 {char2} 输入
第28页 共36页 2011年07月20日 13:20
29. 经验与技巧
mysql 中的技巧
introducer: _charset 'literal' [COLLATE collation]
LENGTH(...) / CHAR_LENGTH(...)
CONVERT(expr USING charset)
HEX(...) / UNHEX(...)
CHAR(... USING charset)
CHARSET(...) / COLLATION(...)
SHOW VARIABLES LIKE 'char%'
SHOW CHARSET / SHOW COLLATION
mysql 客户端程序中可以用 C charset 代替较长的 SET NAMES charset
第29页 共36页 2011年07月20日 13:20
31. 经验与技巧
对于mysql PHP API,一般页面级的PHP程序总运行时间较短,在连接到数据库以后显式用SET NAMES语句设置一次连接字符集即可;但当使用长连接时,请注意保持连接通畅并在断开重
连后用SET NAMES语句显式重置连接字符集
my.cnf中的default_character_set设置只影响mysql命令连接服务器时的连接字符集,不会对使用libmysqlclient库的应用程序产生任何作用!
对字段进行的SQL函数操作通常都是以内部操作字符集进行的,不受连接字符集设置的影响。
第31页 共36页 2011年07月20日 13:20
33. 答疑解惑
常用编码的种类 / 编码的存储方式 / 编码之间的优点缺点比较 / 各种编码大部分应用场合?
各种语言怎么处理编码 / 内部怎么存储 / locale 的影响 / 源代码、输入输出、内部存储中的编码影响?
编码的自动转化、自动识别的根据?(encv, iconv,enca,dos2unix,unix2dos...)
各种浏览器对于 URL 的编码是什么码?如何解码?有什么问题?通用性如何?
数据库编码相关,如库表的统一性?
针对各种不同的编码,有没有统一的处理方法?
Other questions?
第33页 共36页 2011年07月20日 13:20