字符集和字符集编码其实并不难懂,但是很多人在这上面被坑(尤其是使用中文或者其他非 ASCII 语言的人)。本文就是想要简单的讲述字符编码相关的内容,但是并不会涉及到特别深入的原理。
字符集设定正确,没有对应的字体所造成的看到“������”或“□□□□”的情况不在本文讨论的范围内。
通常容易让人犯晕的是:字符集、字符编码这两个概念。例如 HZ / EUC-CN 和 GB2312,UTF-8、UTF16 / UCS-2 和 Unicode / ISO 10646。为了避免这个问题,首先将字符集、字符编码区分清楚。
字符集(Character set,Charset):一个特定的字符集合。字符是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等。通常,字符集会有配套的字符编码方式。例如 GB2312 / GBK 使用的区位码表。
字符编码(Character encoding):在字符(如英文字母、汉字)与其他东西(如数字、脉冲等)之间建立映射关系的编码系统。在本文特指字符 <-> 数字映射。有的编码方式直接采用已有的字符集,如 HZ 直接使用 GB2312 的字符集。
以计算机为基础的信息处理系统利用元件(硬件)不同状态的组合来存储和处理信息,元件不同状态的组合能代表数字系统的数字,因此字符编码就是将符号转换为计算机可以接受的数字系统的数,称为数字代码。
通常我们以“字符集”指代“字符集及其字符编码方式”,因此会有 “UTF-8 字符集”这种说法。这个叫法在 Unicode Transformation Format(UTF)(中文译名:Unicode 转换格式)出现之前通常不会造成困扰(一个例外是 HZ),各种字符集都包含与之对应的编码方式。
MS-DOS、Windows,IBM OS/2 的 Code Page( CP )实际上也是字符集+字符编码方式。例如 CP936 可以近似认为是 GBK 的别名。
Unix 所使用的 Extended Unix Code 是一个字符集和字符编码集合。EUC-CN 可以近似的认为是 GB2312 的别名,EUC-KR 是基于 KS X 1001 和 KS X 1003 字符集发展而来的变长编码方式。
Unicode Transformation Format(UTF) 则没有定义字符集,后面会提到,UTF-8 实际上是一种使用 Unicode 字符集的编码方式。
以下只列举中文环境下常见的字符集和编码方式。
当前中国大陆常用的字符集为 GBK( CP936 ),Unicode,GB18030。GB2312 有少量老系统使用。有少量 UNIX 类操作系统控制台默认使用 ISO 8859-1( LATIN-1 )字符集。
American Standard Code for Information Interchange(中文译名:美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统。它主要用于显示现代英语,其扩展版本EASCII可以部分支持其他西欧语言。当前通用的绝大部分字符集及其编码方式均与 ASCII 保持兼容。
ASCII 第一次以规范标准的型态发表是在1967年,最后一次更新则是在1986年,至今为止共定义了128个字符。其中33个字符无法显示,多数都已是陈废的控制字符。
Hex | 缩写 | Unicode 符号 | 脱出字元表示法 | 转义字符 | 名称 | 含义 |
---|---|---|---|---|---|---|
0 | NUL | ␀ | ^@ | \0 | 空字符(NULL) | 标识字符串结尾,特别是在 C/C++ 语言中。 |
1 | SOH | ␁ | ^A | 标题开始(Start of Heading) | 标识消息头开始。 | |
2 | STX | ␂ | ^B | 本文开始(Start of Text) | 标识文本头开始。 | |
3 | ETX | ␃ | ^C | 本文结束(End of Text) | 通常被用作中断或者终止程序或进程的“终止”字符(Ctrl-C)。 | |
4 | EOT | ␄ | ^D | 传输结束(End of Transmission) | 在 Unix 上被用于标记“文本结束”,或登出终端。 | |
5 | ENQ | ␅ | ^E | 请求(Enquiry) | 触发一次请求应答的信号。 | |
6 | ACK | ␆ | ^F | 确认回应(Acknowledge) | 应答 ENQ 信号,或表明一次信息接收成功。 | |
7 | BEL | ␇ | ^G | \a | 响铃(Bell) | 原本用于触发一次终端响铃。后来也被用于触发一次视觉响铃(某些终端在收到 BEL 信号的时候会闪烁)。 |
8 | BS | ␈ | ^H | \b | 退格(Backspace) | 将光标向左移动一位。在输入的时候,BS 信号会删除光标左边的字符。在输出的时候,老式计算机用 BS 信号来打印带音调的字符。例如 à 可以通过三个字符的序列 a BS ` (Hex:0x61 0x08 0x60)打印。这个做法现在已经废弃。 |
9 | HT | ␉ | ^I | \t | 水平定位符(Horizontal Tabulation) | 定位至下一个制表键停止点 |
0A | LF | ␊ | ^J | \n | 换行(Line Feed) | 在打字机、打印机和部分终端模拟器上,将光标移至下一行,但是不改变光标的列位置。在 Unix 系统中用于标记行结尾。在 MS-DOS,Windows 和许多网络标准中,LF 跟在 CR 后面,共同组成行结尾标记。 |
0B | VT | ␋ | ^K | \v | 垂直定位符(Vertical Tabulation) | 定位至下一个行制表键停止点 |
0C | FF | ␌ | ^L | \f | 换页(Form Feed) | 在打印机中,装载下一页。在许多编程语言里被当作留白,也可能被用于标记代码的逻辑分隔。在部分终端模拟器中,用于清屏。 |
0D | CR | ␍ | ^M | \r | 回车(Carriage Return) | 最初被用于将光标移至第0列,但是不改变光标的行位置。在 Mac OS(Mac OS X 之前的版本)以及更早的 Apple II 和 Commodore 64 中,被用于标记行结尾。在 MS-DOS,Windows 和许多网络标准中,CR 后跟 LF,共同组成行结尾标记。键盘上的 Enter 或 Return 键发送 CR,但是可能会被系统(或终端程序)转换成其他“行结束”标识符。 |
0E | SO | ␎ | ^N | 取消变换(Shift out) | 最初用于在西里尔语和拉丁语之间切换。西里尔ASCII定义中,KOI-7用到了Shift字符。拉丁语用Shift去改变打印机的字体。在此种用途中,SO用于产生双倍宽度(全角)的字符。 | |
0F | SI | ␏ | ^O | 启用变换(Shift in) | 最初用于西里尔语和拉丁语之间切换。西里尔ASCII定义中,KOI-7用到了Shift字符。拉丁语用Shift去改变打印机的字体。在此种用途中,SI用于打印半角字符。 | |
10 | DLE | ␐ | ^P | 数据链路转义(Data Link Escape) | 如果数据流中检测到了DLE,数据接收端则对其后面接下来的数据流中的字符,另作处理。关于具体如何处理这些字符,ASCII规范中没有具体定义,由对应的通信协议自信规定。 | |
11 | DC1 | ␑ | ^Q | 设备控制一(XON) | 在通信被控制码XOFF中断之后,重新开始信息传输。可以将那些由于终端或者主机方面偶尔出现错误的XOFF控制码而中断的通信解锁,使其正常通信。 | |
12 | DC2 | ␒ | ^R | 设备控制二(Device Control Two) | ||
13 | DC3 | ␓ | ^S | 设备控制三(XOFF) | 通常用于临时中断数据流。 | |
14 | DC4 | ␔ | ^T | 设备控制四(Device Control Four) | ||
15 | NAK | ␕ | ^U | 负面响应(Negative Acknowledge) | 用于已建立连接中一端对另一端进行负面响应。在二进制同步通信协议中, NAK 被用于反馈之前收到的数据块存在错误,并且已经做好接收重传的准备。在多点系统中,NAK 被用于发送“未准备就绪”响应。 | |
16 | SYN | ␖ | ^V | 同步用暂停(Synchronous Idle) | 在同步传输系统中被用作数据终端之间同步建立的信号,通常单独传输。 | |
17 | ETB | ␗ | ^W | 区块传输结束(End of Transmission Block) | 在数据被分为多块进行传输的时候,用于标识数据块尾部。 | |
18 | CAN | ␘ | ^X | 取消(Cancel) | 用于标识此前的数据有误或应当丢弃。 | |
19 | EM | ␙ | ^Y | 介质末端(End of medium) | 用于标识到达顺序存储介质(例如磁带或纸张)末尾。这里标识的是逻辑末尾,不一定必须是物理结尾。 | |
1A | SUB | ␚ | ^Z | 替换(Substitute) | 原计划用于标识收到无效字符。如果在同频带传输过程中,SUB 所表达的错误没有必要,通常会被用于其他目的。 | |
1B | ESC | ␛ | ^[ | [ | 退出(Escape) | 通常键盘上的 ESC 键会发送这个字符。用于开始一段控制码的扩展字符。新的技术可能需要新的控制命令,ESC 可以用作这些字符命令的起始标志。ESC 广泛用于打印机和终端,控制设备设置,比如字体,字符位置和颜色等等(如:Shell 的颜色转义序列)。在基于 ISO/IEC 2022 标准的系统中,如果有使用其他 C0 控制代码集,0x1B 仍然通常被要求作为 ESC 字符。 转义字符 \e 并不是 ISO C 或者其他许多编程语言标准的一部分,但是能够被许多编译器识别,包括 gcc 和 clang。 |
1C | FS | ␜ | ^\ | 文件分隔符(File Separator) | 用于分隔顺序存储介质中的文件。 | |
1D | GS | ␝ | ^] | 分组符(Group Separator) | 用于分隔顺序存储介质中的分组。大部分情况下,数据库的建立,都和表有关,包含了对应的记录。同一个表中的所有的记录,属于同一类型。不同的表中的记录,属于对应的不同的类型。GS 用来分隔串行数据存储系统中的不同的组。 | |
1E | RS | ␞ | ^^ | 记录分隔符(Record Separator) | 用于分隔顺序存储介质中同一个文件或同一组内的记录(类似数据库中的“行”)。 | |
1F | US | ␟ | ^_ | 单元分隔符(Unit Separator) | 用于分隔顺序存储介质中的存储单元,这是最小的存储单位(类似数据库中的“列”)。 | |
20 | 空格 | |||||
21 | ! | ! | ! | |||
22 | ” | ” | ” | |||
23 | # | # | # | |||
24 | $ | $ | $ | |||
25 | % | % | % | |||
26 | & | & | & | |||
27 | ’ | ’ | ’ | |||
28 | ( | ( | ( | |||
29 | ) | ) | ) | |||
2A | * | * | * | |||
2B | + | + | + | |||
2C | , | , | , | |||
2D | - | - | - | |||
2E | . | . | . | |||
2F | / | / | / | |||
30 | 0 | 0 | 0 | |||
31 | 1 | 1 | 1 | |||
32 | 2 | 2 | 2 | |||
33 | 3 | 3 | 3 | |||
34 | 4 | 4 | 4 | |||
35 | 5 | 5 | 5 | |||
36 | 6 | 6 | 6 | |||
37 | 7 | 7 | 7 | |||
38 | 8 | 8 | 8 | |||
39 | 9 | 9 | 9 | |||
3A | : | : | : | |||
3B | ; | ; | ; | |||
3C | < | < | < | |||
3D | = | = | = | |||
3E | > | > | > | |||
3F | ? | ? | ? | |||
40 | @ | @ | @ | |||
41 | A | A | A | |||
42 | B | B | B | |||
43 | C | C | C | |||
44 | D | D | D | |||
45 | E | E | E | |||
46 | F | F | F | |||
47 | G | G | G | |||
48 | H | H | H | |||
49 | I | I | I | |||
4A | J | J | J | |||
4B | K | K | K | |||
4C | L | L | L | |||
4D | M | M | M | |||
4E | N | N | N | |||
4F | O | O | O | |||
50 | P | P | P | |||
51 | Q | Q | Q | |||
52 | R | R | R | |||
53 | S | S | S | |||
54 | T | T | T | |||
55 | U | U | U | |||
56 | V | V | V | |||
57 | W | W | W | |||
58 | X | X | X | |||
59 | Y | Y | Y | |||
5A | Z | Z | Z | |||
5B | [ | [ | [ | |||
5C | \ | \ | \ | |||
5D | ] | ] | ] | |||
5E | ^ | ^ | ^ | |||
5F | _ | _ | _ | |||
60 | ` | ` | ` | |||
61 | a | a | a | |||
62 | b | b | b | |||
63 | c | c | c | |||
64 | d | d | d | |||
65 | e | e | e | |||
66 | f | f | f | |||
67 | g | g | g | |||
68 | h | h | h | |||
69 | i | i | i | |||
6A | j | j | j | |||
6B | k | k | k | |||
6C | l | l | l | |||
6D | m | m | m | |||
6E | n | n | n | |||
6F | o | o | o | |||
70 | p | p | p | |||
71 | q | q | q | |||
72 | r | r | r | |||
73 | s | s | s | |||
74 | t | t | t | |||
75 | u | u | u | |||
76 | v | v | v | |||
77 | w | w | w | |||
78 | x | x | x | |||
79 | y | y | y | |||
7A | z | z | z | |||
7B | { | { | { | |||
7C | | | | | | | |||
7D | } | } | } | |||
7E | ~ | ~ | ~ | |||
7F | DEL | ␡ | ^? | 删除(Delete) | 这个字符并不在 C0 控制代码区间里。最初被用于标记打孔带上被删除的字符。大多数打孔带都是7位,所有字符都可以被修改为 0x7F 对应的二进制代码 1111111 (7个孔位全部穿孔),以此来标识这个字符被删除。在 VT100 兼容终端上,这个字符可以通过标记为 ⌫ 的按键产生,这个键在现代计算机中被称作 Backspace。这个字符与 PC 键盘上的 Delete 按键没有任何关系。” |
ISO 8859 是国际标准化组织及国际电工委员会联合制定的一系列8位字符集的标准,现时定义了15个字符集,用于表示其他拉丁字母、西里尔字母、希腊语、泰语、阿拉伯语、希伯来语。兼容 ASCII,增补的 96 个字符根据不同的分组,对应不同的字符集合。
部分 Unix 类系统默认使用 ISO 8859-1(Latin-1)作为默认字符集。
ISO 8859 的15个分组(Part)如下:
分组 | 别名 | 说明 |
---|---|---|
ISO 8859-1 | Latin-1 | 西欧语言 |
ISO 8859-2 | Latin-2 | 中欧语言 |
ISO 8859-3 | Latin-3 | 南欧语言,世界语也可以用此字符集显示 |
ISO 8859-4 | Latin-4 | 北欧语言 |
ISO 8859-5 | Cyrillic | 斯拉夫语言 |
ISO 8859-6 | Arabic | 阿拉伯语 |
ISO 8859-7 | Greek | 希腊语 |
ISO 8859-8 | Hebrew | 希伯来语 |
ISO 8859-9 | Latin5、Turkish | 它把Latin-1的冰岛语字母换走,加入土耳其语字母 |
ISO 8859-10 | Latin6、Nordic | 北日耳曼语支,用来代替Latin-4 |
ISO 8859-11 | Thai | 泰语,从泰国的 TIS620 标准字集演化而来 |
ISO 8859-13 | Latin-7、Baltic Rim | 波罗的语族 |
ISO 8859-14 | Latin-8、Celtic | 凯尔特语族 |
ISO 8859-15 | Latin-9 | 西欧语言,加入Latin-1欠缺的芬兰语字母和大写法语重音字母,以及欧元(€)符号 |
ISO 8859-16 | Latin-10 | 东南欧语言。主要供罗马尼亚语使用,并加入欧元符号。 |
由于 ISO 8859 采用8位编码,在通过 ssh 或者 telnet 等方式登录使用 ISO 8859-1 字符集的 Unix 系统时,终端可以基本正确的显示其中的双字节(GBK、BIG5 等)或多字节(GB18030、UTF-8 等)编码的中文字符。但是在处理的时候系统将仍然按照单个字节进行处理,因此 GBK 编码的“中(0xD6 0xD0)”字将被当作2个字符处理,这就是“半个汉字”问题的由来。
GB2312 规定:一个小于127的字符的意义与原来相同,但两个大于127的字符连在一起时,就表示一个汉字,前面的一个字节(他称之为高字节)从0xA1用到 0xF7,后面一个字节(低字节)从0xA1到0xFE。GB2312 共收录 6763 个汉字,覆盖 99% 以上的使用场景。
GB13000(全称 GB 13000.1-93《信息技术通用多八位编码字符集(UCS)第一部分:体系结构与基本多文种平面》) 是一个基于 ISO/IEC 10646 字符集的中文字符集和编码方式标准。与 GB2312 不兼容,基本上未被工业界实际支持过。
GBK 是在 GB2312 的基础上,由微软利用 GB2312 未使用的编码空间,收录 GB13000 全部字符制定而成,这是一个 业界标准,并不是一个国家标准。GBK 的编码方式 基本兼容 GB2312,其中的区别参考这里。完整的 GBK 码表可以参考这里。
GB2312 和 GBK 双字节符号可以表达的空间如下图所示。绿色和黄色区域是 GBK 的编码,红色是用户定义区域。没有颜色的区域是不正确的代码组合:
Big5 也被称作大五码,是繁体中文社区(包括台湾正体中文)最常用的汉字字符集,这是一个 业界标准 而非国家标准的双字节字符集。
在繁体中文社区中,Big5 有多个变种,常见的包括:
编码空间 | 说明 |
---|---|
0x81 ~ 0xFE | ASCII 字符 |
0x8140 ~ 0xA0FE | 保留给用户自定义字符(造字区) |
0xA140 ~ 0xA3BF | 标点符号、希腊字母及特殊符号,包括在0xA259-0xA261,安放了九个计量用汉字:’兙’、’兛’、’兞’、’兝’、’兡’、’兣’、’嗧’、’瓩’、’糎’。 |
0xA3C0 ~ 0xA3FE | 保留。此区没有开放作造字区用。 |
0xA440 ~ 0xC67E | 常用汉字,先按笔划再按部首排序。 |
0xC6A1 ~ 0xC8FE | 保留给用户自定义字符(造字区) |
0xC940 ~ 0xF9D5 | 次常用汉字,亦是先按笔划再按部首排序。 |
0xF9D6 ~ 0xFEFE | 保留给用户自定义字符(造字区)。在倚天中文系统中,这个空间还包括’碁’、’銹’、’恒’、’裏’、’墻’、’粧’、’嫺’ 7个汉字和34个其他符号。 |
HZ 严格来说是一个使用 GB2312 字符集的编码方式,1989年由斯坦福大学的李楓峰设计,曾被广泛应用于中文 USENET 和邮件编码。
在 HZ 编码方式中,“~{” 和 “~}” 被当作转义字符,由它们括起来的内容将被当作 GB2312 内码处理,在此之外的内容仍被当作 ASCII 处理。HZ 编码的“中文”二字看起来是这样的:
~{VPND~}
Unicode(中文有多个译名:万国码、国际码、统一码、单一码)是一种字符编码标准(字符集),由统一码联盟负责拟定。
Universal Character Set(UCS)(中文译名:通用字符集、通用多八位编码字符集、广用多八比特编码字元集)是 ISO 10646 标准定义的字符编码标准(字符集)。UCS 分为3个实现级别:
Unicode 自 2.0 版本以后,与 UCS Level 3 兼容。本文中一律以 Unicode 称之。
在表示一个Unicode的字符时,通常会用“U+”然后紧接着一组十六进制的数字来表示这一个字符。在基本多文种平面(英文:Basic Multilingual Plane,简写BMP。又称为“0号平面”、plane 0)里的所有字元,要用四位十六进制数(例如 U+4AE0,共支持六万多个字符);在0号平面以外的字符则需要使用五位或六位十六进制数。
当前的 Unicode 版本为7.0,2014年6月16日发布,收录了超过10万个字符。Unicode 将6字节长度的编码空间划分为17个平面(Plane),每个平面有65536个代码点,目前只使用了少数平面。其中0号平面(U+0000 ~ U+FFFF)被称作基本多文种平面(Basic Multilingual Plane,BMP),用于存放现有的主要文字、符号,编码长度为4字节,其中前256个字符(U+0000 ~ U+00FF)与 ISO 8859-1 兼容。
Unicode 基本多文种平面的示意如下图。每个写着数字的格子代表256个码点。关于 Unicode 字符平面映射的更多内容,参考这里。
Unicode 和 ISO 10646 定义了字符集以及字符到数字映射关系的码表,并没有定义这些数字如何转换成字节流。
例如:0x6C 0x49 究竟应该当作字符 “lI”(U+006C U+0049)处理,还是应该当作字符“汉”(U+6C49)处理?可以规定所有 Unicode 字符长度为6字节,高位补0。这样既浪费空间又破坏了与 Latin-1 的兼容性。
Unicode Transformation Format(UTF)(中文译名:Unicode 转换格式)和 Universal Character Set (UCS) encodings(仍然简称 UCS)。就是定义数字到字节流转换关系的标准。其中 UTF-8 在当前互联网上使用范围最广。其余还有 UTF-16、UCS2、和 UTF-32(UCS-4)。
UTF-8(8-bit Unicode Transformation Format)是一种针对 Unicode 的可变长度字符编码,也是一种前缀码。它可以用来表示Unicode标准中的任何字符,且其编码中的第一个字节仍与 ASCII 兼容这使得原来处理 ASCII 字符的软件无须或只须做少部份修改,即可继续使用。因此,它逐渐成为电子邮件、网页及其他存储或传送文字的应用中,优先采用的编码。互联网工程工作小组(IETF)要求所有互联网协议都必须支持UTF-8编码。
UTF-8使用一至四个字节进行编码,规则如下:
UTF-8 的编码空间如下表所示:
代码点字长 (字节数) |
起始点 | 结束点 | 二进制表示 |
---|---|---|---|
1 | U+0000 | U+007F | 0xxxxxxx |
2 | U+0080 | 0+07FF | 110xxxxx 10xxxxxx |
3 | U+0800 | U+FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
4 | U+10000 | U+1FFFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
5 | U+200000 | U+3FFFFFF | 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
6 | U+4000000 | U+7FFFFFFF | 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx |
对于 UTF-8 编码中的任意字节B,如果B的第一位为0,则B为ASCII码,并且B独立的表示一个字符;
因此,对UTF-8编码中的任意字节,根据第一位,可判断是否为ASCII字符;根据前二位,可判断该字节是否为一个字符编码的第一个字节;根据前四位(如果前两位均为1),可确定该字节为字符编码的第一个字节,并且可判断对应的字符由几个字节表示;根据前五位(如果前四位为1),可判断编码是否有错误或数据传输过程中是否有错误。
针对其他东亚文字的常见字符集有:
如前所述,字符集定义了特定字符到字节流的转换方式。在显示文本的时候我们需要知道对应的字符集,才能正确解码并显示文本。
通常在 HTMl(包括 XHTML)和 XML 页面中,会包含字符集声明。
HTML / XHTML:
<head>
<meta content="text/html; charset=UTF-8" />
</head>
XML
<?xml version="1.0" encoding="UTF-8"?>
关系型数据库以 Database、Table、Column 的结构来组织数据,存储文本所使用的字符集通常存储在表结构中。可以通过查看对应的文档找到查看数据库所使用字符集的方式。常见的数据库系统以及对应的文档地址如下:
只支持在会话(Session)级别设置字符集,实际存储文本时使用的字符集继承自建库时的会话所使用的字符集,且不能更改。
支持在会话、库(Database)、表(Table)、字段(Column)指定字符集。
其他版本的地址如下(请将“$VERSION”更换为对应的版本号): http://dev.mysql.com/doc/refman/$VERSION/en/charset-syntax.html
其他版本的地址如下(请将“$VERSION”更换为对应的版本号): http://www.postgresql.org/docs/$VERSION/static/multibyte.html
CJK Ideograph Extension B、C、D(U+20000 to U+200FF U+2A700 to U+2A7FF U+2B740 to U+2B83F) CJK Conpatibility Ideograph Supplenment (U+2F800 to U+2F8FF)