计算机编码
Persus & Xie
字符集
计算机是以二进制的形式来存储数据的,它只认识0和1两个数字,我们在屏幕上看到的文字,在存储之前都被转换成了二进制,即0和1序列,在显示时也要根据二进制找到对应的字符。
在计算机中,每一个二进制位(bit)
有0和1两种状态;因此对于8个二进制位就可以组合出256种形状,这被称为一个字节(byte)
。也就是说,一个字节可以用来表示256种不同的状态。每一个状态对应一个符号,也就是256个符号,从00000000
到11111111
。
可想而知,特定的文字必然对应这个特定的二进制序列,否则在转换过程中就会发很混乱。所以,就需要一套规范将文字和二进制对应起来,一套软件开发者和计算机公司都要遵守的规范。这样的一套规范称之为字符集 (Cahracter Set)
或者字符编码 (Character Encoding)
。但是,严格地讲,字符集
和字符编码
并不是一个概念,字符集
定义了文字和二进制和对应关系,为每个字符分配了唯一编号。字符编码
则规定了如何将文字的编号存储到计算机中。
字符集为每个字符分配了一个唯一的编号,类似于每个人都有一个身份证编号,通过编号就能找到对用的字符也可以将字符集
理解为一个很大的表格,它列出了所有的字符和二进制序列的对应对应关系,计算机显示文字或者存储文字就是一个查表的过程。
在计算机发展的过程中,曾先后出现了几十上百种的字符集
,至今有些仍在使用,但是有些却因为某些原因消亡了。
计算机诞生于美国,所以在研发它的过程中,一个需要考虑的问题是:如何将二进制和英文字母对应起来。曾经,各个公司和研发机构都有自己的编码规则,这些编码规则并不统一。
比如,IBM发明的EBCDIC
编码;中国制定了GB2312
编码规则,用以编译中文;日本把日文编到Shift_JIS
里,韩国把韩文编到Euc-kr
里。
本文主要讲述的是一门专门针对英文的字符集
—ASCII
编码,以及全球统一的编码**UTF-8
编码**。
ASCII 编码
ASCII 是American Standard Code for Information Interchange
的缩写,翻译过来是“美国信息交换标准代码”。ASCII 的标准版本于 1967 年第一次发布,最后一次更新则是在 1986 年,迄今为止共收录了 128 个字符,包含了基本的拉丁字母(英文字母)、阿拉伯数字(也就是 1234567890)、标点符号(,.!
等)、特殊符号(@#$%^&*
等)以及一些具有控制功能的字符所谓ASCII
码,就是将英文字母和常用符号用特定的数字去表达。 比如:
字母
A
用ASCII编码是十进制的65
,二进制的01000001
;字符
0
用ASCII编码是十进制的48
,二进制的00110000
,注意字符'0'
和整数0
是不同的;
我们可以将ASCII
编码理解为一个英汉字典,中文代表128个字符,英文代表计算机中所对应的二进制序列。这种形式让计算机和人类的交互变得更便捷。在 ASCII 编码中,大写字母、小写字母和阿拉伯数字都是连续分布的(见下表),这给程序设计带来了很大的方便。例如要判断一个字符是否是大写字母,就可以判断该字符的 ASCII 编码值是否在 65~90 的范围内。
将ASCII 可显示字符转换为十六进制或者二进制
在制订ASCII编码的过程中,首先是对128个字符用以十进制从0~127排序,每个字符对应着指定的十进制数字,如果要用其中某个字符转换为二进制或者16进制,是将其对应的十进制数字转换为二进制或者十六进制;举例如下:
在ASCII编码表中,字符Q
所对应的十进制数字为81,那么将整数81转换为二进制或者十六进制,结果就是字符Q
所对应的二进制或者十六进制序列。
转十六进制的过程图如下:十进制转81到十六进制为51
转二进制的过程图如下:十进制数字81转十六进制为1010001
ASCII 编码表
标准 ASCII 编码共收录了 128 个字符,其中包含了 33 个控制字符和 95 个可显示字符:
二进制 | 十进制 | 十六进制 | 字符/缩写 | 解释 |
---|---|---|---|---|
00000000 | 0 | 00 | NUL (NULL) | 空字符 |
00000001 | 1 | 01 | SOH (Start Of Headling) | 标题开始 |
00000010 | 2 | 02 | STX (Start Of Text) | 正文开始 |
00000011 | 3 | 03 | ETX (End Of Text) | 正文结束 |
00000100 | 4 | 04 | EOT (End Of Transmission) | 传输结束 |
00000101 | 5 | 05 | ENQ (Enquiry) | 请求 |
00000110 | 6 | 06 | ACK (Acknowledge) | 回应/响应/收到通知 |
00000111 | 7 | 07 | BEL (Bell) | 响铃 |
00001000 | 8 | 08 | BS (Backspace) | 退格 |
00001001 | 9 | 09 | HT (Horizontal Tab) | 水平制表符 |
00001010 | 10 | 0A | LF/NL(Line Feed/New Line) | 换行键 |
00001011 | 11 | 0B | VT (Vertical Tab) | 垂直制表符 |
00001100 | 12 | 0C | FF/NP (Form Feed/New Page) | 换页键 |
00001101 | 13 | 0D | CR (Carriage Return) | 回车键 |
00001110 | 14 | 0E | SO (Shift Out) | 不用切换 |
00001111 | 15 | 0F | SI (Shift In) | 启用切换 |
00010000 | 16 | 10 | DLE (Data Link Escape) | 数据链路转义 |
00010001 | 17 | 11 | DC1/XON (Device Control 1/Transmission On) | 设备控制1/传输开始 |
00010010 | 18 | 12 | DC2 (Device Control 2) | 设备控制2 |
00010011 | 19 | 13 | DC3/XOFF (Device Control 3/Transmission Off) | 设备控制3/传输中断 |
00010100 | 20 | 14 | DC4 (Device Control 4) | 设备控制4 |
00010101 | 21 | 15 | NAK (Negative Acknowledge) | 无响应/非正常响应/拒绝接收 |
00010110 | 22 | 16 | SYN (Synchronous Idle) | 同步空闲 |
00010111 | 23 | 17 | ETB (End of Transmission Block) | 传输块结束/块传输终止 |
00011000 | 24 | 18 | CAN (Cancel) | 取消 |
00011001 | 25 | 19 | EM (End of Medium) | 已到介质末端/介质存储已满/介质中断 |
00011010 | 26 | 1A | SUB (Substitute) | 替补/替换 |
00011011 | 27 | 1B | ESC (Escape) | 逃离/取消 |
00011100 | 28 | 1C | FS (File Separator) | 文件分割符 |
00011101 | 29 | 1D | GS (Group Separator) | 组分隔符/分组符 |
00011110 | 30 | 1E | RS (Record Separator) | 记录分离符 |
00011111 | 31 | 1F | US (Unit Separator) | 单元分隔符 |
01111111 | 127 | 7F | DEL (Delete) | 删除 |
二进制 | 十进制 | 十六进制 | 字符/解释 |
---|---|---|---|
00100000 | 32 | 20 | (Space)/空格 |
00100001 | 33 | 21 | ! |
00100010 | 34 | 22 | " |
00100011 | 35 | 23 | # |
00100100 | 36 | 24 | $ |
00100101 | 37 | 25 | % |
00100110 | 38 | 26 | & |
00100111 | 39 | 27 | ’ |
00101000 | 40 | 28 | ( |
00101001 | 41 | 29 | ) |
00101010 | 42 | 2A | * |
00101011 | 43 | 2B | + |
00101100 | 44 | 2C | , |
00101101 | 45 | 2D | - |
00101110 | 46 | 2E | . |
00101111 | 47 | 2F | / |
00110000 | 48 | 30 | 0 |
00110001 | 49 | 31 | 1 |
00110010 | 50 | 32 | 2 |
00110011 | 51 | 33 | 3 |
00110100 | 52 | 34 | 4 |
00110101 | 53 | 35 | 5 |
00110110 | 54 | 36 | 6 |
00110111 | 55 | 37 | 7 |
00111000 | 56 | 38 | 8 |
00111001 | 57 | 39 | 9 |
00111010 | 58 | 3A | : |
00111011 | 59 | 3B | ; |
00111100 | 60 | 3C | < |
00111101 | 61 | 3D | = |
00111110 | 62 | 3E | > |
00111111 | 63 | 3F | ? |
01000000 | 64 | 40 | @ |
01000001 | 65 | 41 | A |
01000010 | 66 | 42 | B |
01000011 | 67 | 43 | C |
01000100 | 68 | 44 | D |
01000101 | 69 | 45 | E |
01000110 | 70 | 46 | F |
01000111 | 71 | 47 | G |
01001000 | 72 | 48 | H |
01001001 | 73 | 49 | I |
01001010 | 74 | 4A | J |
01001011 | 75 | 4B | K |
01001100 | 76 | 4C | L |
01001101 | 77 | 4D | M |
01001110 | 78 | 4E | N |
01001111 | 79 | 4F | O |
01010000 | 80 | 50 | P |
01010001 | 81 | 51 | Q |
01010010 | 82 | 52 | R |
01010011 | 83 | 53 | S |
01010100 | 84 | 54 | T |
01010101 | 85 | 55 | U |
01010110 | 86 | 56 | V |
01010111 | 87 | 57 | W |
01011000 | 88 | 58 | X |
01011001 | 89 | 59 | Y |
01011010 | 90 | 5A | Z |
01011011 | 91 | 5B | [ |
01011100 | 92 | 5C | \ |
01011101 | 93 | 5D | ] |
01011110 | 94 | 5E | ^ |
01011111 | 95 | 5F | _ |
01100000 | 96 | 60 | ` |
01100001 | 97 | 61 | a |
01100010 | 98 | 62 | b |
01100011 | 99 | 63 | c |
01100100 | 100 | 64 | d |
01100101 | 101 | 65 | e |
01100110 | 102 | 66 | f |
01100111 | 103 | 67 | g |
01101000 | 104 | 68 | h |
01101001 | 105 | 69 | i |
01101010 | 106 | 6A | j |
01101011 | 107 | 6B | k |
01101100 | 108 | 6C | l |
01101101 | 109 | 6D | m |
01101110 | 110 | 6E | n |
01101111 | 111 | 6F | o |
01110000 | 112 | 70 | p |
01110001 | 113 | 71 | q |
01110010 | 114 | 72 | r |
01110011 | 115 | 73 | s |
01110100 | 116 | 74 | t |
01110101 | 117 | 75 | u |
01110110 | 118 | 76 | v |
01110111 | 119 | 77 | w |
01111000 | 120 | 78 | x |
01111001 | 121 | 79 | y |
01111010 | 122 | 7A | z |
01111011 | 123 | 7B | { |
01111100 | 124 | 7C | | |
01111101 | 125 | 7D | } |
01111110 | 126 | 7E | ~ |
上表列出的是标准的 ASCII 编码,它共收录了 128 个字符,用一个字节中较低的 7 个比特位(bit)
足以表示(
2
7
2^7
27 = 128),所以还会空闲下一个比特位,所以有一个比特位被闲置。
非ASCII编码
以英语为母语国家用128个符号编码就够了,但是用来表示其他语言,128个符号是不够的。比如,在法语中,字母é
上方有注音符号,它就无法用ASCII码表示。于是,一些欧洲国家就决定,利用字节中闲置的最高位编入新的符号。比如,法语中的é
的编码为130(二进制10000010)。这样的话,这些欧洲国家使用的编码体系,可以表示最多256个符号。
但是,这里又出现了新的问题。不同的国家有不同的字母,因此,哪怕它们都使用256个符号的编码方式,代表的字母却不一样。比如,130在法语编码中代表了é
,在希伯来语编码中却代表了字母Gimel (ג)
,在俄语编码中又会代表另一个符号。但是不管怎样,所有这些编码方式中,0–127表示的符号是一样的,不一样的只是128–255的这一段。
至于亚洲国家的文字,使用的符号就更多了,汉字就多达10万左右。一个字节只能表示256种符号,肯定是不够的,就必须使用多个字节表达一个符号。比如,简体中文常见的编码方式是GB2312,使用两个字节表示一个汉字,所以理论上最多可以表示256x256=65536个符号。
中文编码的问题需要专文讨论,这篇文章不作分析。这里只指出,虽然都是用多个字节表示一个符号,但是GB类的汉字编码与后文的Unicode和UTF-8是毫无关系的。
Unicode
世界上存在着多种编码方式,同一个二进制数字可以被解释成不同的符号。因此,要想打开一个文本文件,就必须知道它的编码方式,否则用错误的编码方式解读,就会出现乱码。为什么不同系统在打开同一个文件常常出现乱码?就是因为两个系统使用的编码方式不一样。
假设,如果有一种编码,将世界上所有的符号都纳入其中。每一个符号都给予一个独一无二的编码,那么乱码问题就会消失。这就是Unicode,就像它的名字都表示的,这是一种所有符号的编码。
Unicode当然是一个很大的集合,现在的规模可以容纳100多万个符号。每个符号的编码都不一样,比如,U+0639
表示阿拉伯字母Ain,U+0041
表示英语的大写字母A
,U+4E25
表示汉字严
。具体的符号对应表,可以查询unicode.org,或者专门的汉字对应表。
Unicode 的问题
需要注意的是,Unicode只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。
比如,汉字严
的unicode是十六进制数4E25
,转换成二进制数足足有15位100111000100101
,也就是说这个符号的表示至少需要2个字节。表示其他更大的符号,可能需要3个字节(byte
)或者4个字节(byte
),甚至更多。
y因此会出现两个严重的问题,第一个问题是,如何才能区别Unicode和ASCII?计算机怎么知道三个字节表示一个符号,而不是分别表示三个符号呢?第二个问题是,我们已经知道,英文字母只用一个字节表示就够了,如果Unicode统一规定,每个符号用三个或四个字节表示,那么每个英文字母前都必然有二到三个字节是0,这对于存储来说是极大的浪费,文本文件的大小会因此大出二三倍,这是无法接受的。
它们造成的结果是:
- 出现了Unicode的多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示Unicode。
- Unicode在很长一段时间内无法推广,直到互联网的出现。
UTF-8
互联网的普及,急需一种统一的编码方式。UTF-8就是在互联网上使用最广的一种Unicode的实现方式。其他实现方式还包括UTF-16(字符用两个byte
或四个byte
表示)和UTF-32(字符用四个字节表示),不过在互联网上基本不用。重复一遍,这里的关系是,UTF-8是Unicode的实现方式之一。
UTF-8最大的一个特点,就是它是一种变长的编码方式。它可以使用1~4个字节表示一个符号,根据不同的符号而变化字节长度。
UTF-8的编码规则很简单,只有二条:
-
对于单字节的符号,字节的第一位设为0,后面7位为这个符号的unicode码。因此对于英语字母,UTF-8编码和ASCII码是相同的。
-
对于n字节的符号(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字符x表示可用编码的位。
Unicode符号范围 | UTF-8编码方式
(十六进制) | (二进制)
--------------------±--------------------------------------------
0000 0000-0000 007F | 0xxxxxxx >>>>>>> 1个字节
0000 0080-0000 07FF | 110xxxxx 10xxxxxx >>>>>>> 2个字节
0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx >>>>>>> 3个字节
0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx >>>>>>> 4个字节
下面,还是以汉字"严"为例,演示如何实现UTF-8编码。
已知严
的unicode是4E25(100111000100101)
,根据上表,可以发现4E25处在第三行的范围内(0000 0800-0000 FFFF),因此严
的UTF-8编码需要三个字节,即格式是1110xxxx 10xxxxxx 10xxxxxx
。然后,从严
的最后一个二进制位开始,依次从后向前填入格式中的x
,多出的位补0
。这样就得到了,严
的UTF-8编码是11100100 10111000 10100101
,转换成十六进制就是E4B8A5
。
总结
本文是在写程序处理数据,在对数据进行编码的过程中遇到一系列问题,所以在本篇中作出梳理。本文主要涉及到编码的认识,编码的过程,ASCII编码,Unicode编码。