目录
电脑是由电路板组成,电路板里面集成了无数的电阻和电容,交流电经过电容的时候,电压比较低,记为低电平,用0表示,交流电流过电阻的时候,电压比较高,记为高电平,用1来表示;所以每一个1和0在计算机中被称为位,也就是bit位。然而,如果使用一个位来表示计算机中的最小存储单元,那么这个存储单元只能存储0或者1,存储的范围太小了,所以我们规定用8个bit为一组来表示计算机的最小存储单元。8个位每个位上能存储0或者1,则byte的存储范围则是00000000-11111111(换算成整数即0-255),这个最小存储单元就是byte字节。
计算机的底层只能存储0和1,如果是日常生活中遇到的数字,比如 127,这个可以通过10进制和二进制的转换从而让计算机存储01111111,但是如果计算机存储类似于汉字、英文字符、符号等内容,是如何存储的呢?
根据上图解释说明,计算机提供了很多的编码表记录了字符和数字的一一对应关系,编码就是把字符对应编码表中的码值存储在电脑中,而解码则是把码值在编码表中的对应的字符展现出来。
注意:计算机中存储一个数,是用二进制来表示的,比如 存储127,那么计算机的底层是0111 1111,人看这些二进制的数值都是眼花缭乱的,如何方便而规整的表示这些二进制数呢,不妨引入十六进制,二进制换算成十六进制,则是每四位为一组转换为16进制数即可,比如0111 1111这个数前4位0111转换为7,后4位转换为F,则最终的十六进制是7F,一般繁琐的二进制数使用十六进制数来表示会比较方便规整,所以人们习惯用十六进制数来表示码值。
世界上虽然有各种各样的字符,但计算机发明之初没有考虑那么多,基本上只考虑了美国的需求,美国大概只需要128个字符,美国就规定了这128个字符的二进制表示方法,这个方法是一个标准,称为ASCII编码,全称是American Standard Code for Information Interchange,美国信息互换标准代码。128个字符用7个位刚好可以表示,计算机存储的最小单位是byte,即8位,ASCII码中最高位设置为0,用剩下的7位表示字符。这7位可以看作数字0到127,ASCII码规定了从0到127个,每个数字代表什么含义。我们先来看数字32到126的含义,如下所示,除了中文之外,我们平常用的字符基本都涵盖了,键盘上的字符大部分也都涵盖了。
32 | 空格 | 33 | ! | 34 | " | 35 | # | 36 | $ | 37 | % | 38 | & | 39 | ' |
40 | ( | 41 | ) | 42 | * | 43 | + | 44 | , | 45 | - | 46 | . | 47 | / |
48 | 0 | 49 | 1 | 50 | 2 | 51 | 3 | 52 | 4 | 53 | 5 | 54 | 6 | 55 | 7 |
56 | 8 | 57 | 9 | 58 | : | 59 | ; | 60 | < | 61 | = | 62 | > | 63 | ? |
64 | @ | 65 | A | 66 | B | 67 | C | 68 | D | 69 | E | 70 | F | 71 | G |
72 | H | 73 | I | 74 | J | 75 | K | 76 | L | 77 | M | 78 | N | 79 | O |
80 | P | 81 | Q | 82 | R | 83 | S | 84 | T | 85 | U | 86 | V | 87 | W |
88 | X | 89 | Y | 90 | Z | 91 | [ | 92 | \ | 93 | ] | 94 | ^ | 95 | _ |
96 | ` | 97 | a | 98 | b | 99 | c | 100 | d | 101 | e | 102 | f | 103 | g |
104 | h | 105 | i | 106 | j | 107 | k | 108 | l | 109 | m | 110 | n | 111 | o |
112 | p | 113 | q | 114 | r | 115 | s | 116 | t | 117 | u | 118 | v | 119 | w |
120 | x | 121 | y | 122 | z | 123 | { | 124 | | | 125 | } | 126 | ~ |
数字32到126表示的这些字符都是可打印字符,0到31和127表示一些不可打印的字符,这些字符一般用于控制目的,这些字符中大部分都是不常用的,下表列出了其中相对常用的字符。
数字 | 缩写/字符 | 解释 | 转义字符 |
0 | NUL(null) | 空字符 | \0 |
8 | BS(backspace) | 退格 | \b |
9 | HT(horizontal tab) | 水平制表符 | \t |
10 | LF(NL line feed,new line) | 换行键 | \n |
13 | CR(carriage return) | 回车键 | \r |
27 | ESC | 换码 | |
127 | DEL(delete) | 删除 |
- public class AsciiTest {
-
- public static void main(String[] args) {
- char c = 9;
- System.out.print(c);
- System.out.println("hello");// hello
- }
-
- }
Ascii码对美国是够用了,但对别的国家而言却是不够的,于是,各个国家的各种计算机厂商就发明了各种各样的编码方式以表示自己国家的字符,为了保持与Ascii码的兼容性,一般都是最高位设置为1.也就是说,当最高位为0时,表示Ascii码,当为1时就是各个国家自己的字符。在这些扩展的编码中,在西欧国家中流行的是ISO 8859-1和Windows-1252,在中国是GB2312,GBK和Big5,我们来逐个来研究这些编码。
ISO 8859-1又称Latin-1,它也是使用一个字节表示一个字符,因为西欧的文字也都是字母拼接,只不过不是26个英文字母罢了,其中0到127与Ascii一样,128到255规定了不同的含义。在128到255中,128到159表示一些控制字符,这些字符也不常用,就不介绍了,160到255表示一些西欧字符,如下所示
160 | ¡ 161 | ¢ 162 | £ 163 | ¤ 164 | ¥ 165 | ¦ 166 | § 167 | ¨ 168 | © 169 | ª 170 | « 171 | ¬ 172 | 173 | ® 174 | ¯ 175 |
° 176 | ± 177 | ² 178 | ³ 179 | ´ 180 | µ 181 | ¶ 182 | · 183 | ¸ 184 | ¹ 185 | º 186 | » 187 | ¼ 188 | ½ 189 | ¾ 190 | ¿ 191 |
À 192 | Á 193 | Â 194 | Ã 195 | Ä 196 | Å 197 | Æ 198 | Ç 199 | È 200 | É 201 | Ê 202 | Ë 203 | Ì 204 | Í 205 | Î 206 | Ï 207 |
Ð 208 | Ñ 209 | Ò 210 | Ó 211 | Ô 212 | Õ 213 | Ö 214 | × 215 | Ø 216 | Ù 217 | Ú 218 | Û 219 | Ü 220 | Ý 221 | Þ 222 | ß 223 |
à 224 | á 225 | â 226 | ã 227 | ä 228 | å 229 | æ 230 | ç 231 | è 232 | é 233 | ê 234 | ë 235 | ì 236 | í 237 | î 238 | ï 239 |
ð 240 | ñ 241 | ò 242 | ó 243 | ô 244 | õ 245 | ö 246 | ÷ 247 | ø 248 | ù 249 | ú 250 | û 251 | ü 252 | ý 253 | þ 254 | ÿ 255 |
ISO 8859-1虽然号称是标准,用于西欧国家,但它连欧元(€)这个符号都没有,因为欧元比较晚,而标准比较早。实际使用中更为广泛的是Windows-1252编码,这个编码与ISO8859-1基本是一样的,区别只在于数字128到159,Windows-1252使用其中的一些数字表示可打印字符,如下所示。
€ 128 | ‚ 130 | ƒ 131 | „ 132 | … 133 | † 134 | ‡ 135 | ˆ 136 | ‰ 137 | Š 138 | ‹ 139 | Œ 140 | Ž 142 | |||
‘ 145 | ’ 146 | “ 147 | ” 148 | • 149 | – 150 | — 151 | ˜ 152 | ™ 153 | š 154 | › 155 | œ 156 | ž 158 | Ÿ 159 |
这个编码中加入了欧元以及一些其他常用的字符,基本上可以认为,ISO 8859-1已被Windows-1252取代,在很多应用程序中,即使文件声明它采用的是ISO 8859-1编码,解析的时候依然被当作Windows-1252编码。
HTML5甚至明确规定,如果文件声明为ISO 8859-1编码,它应该被看做Windows-1252编码。为什么会这样呢?因为大部分人搞不清楚ISO 8859-1和Windows-1252的区别,当他说ISO 8859-1的时候,其实他实际指的是Windows-1252,所以标准干脆就那么强制了。
美国和西欧字符用一个字节就够了,但中文显然是不够的,中文第一个标准是GB2312。GB2312标准主要针对的是简体中文常见字符,包括约7000个汉字,不包括一些罕见词,不包括繁体字,GB2312固定使用两个字节表示汉字,在这两个字节中,最高位都是1,如果是0,就认为是Ascii字符。在这两个字节中,其中第一个字节范围是1010 0001(十进制161)-1111 0111(十进制247),第二个字节范围是1010 0001(十进制161)-1111 1110(十进制254)。
比如,"贤哥"的GB2312编码是
贤 | 哥 |
CF,CD | B8,E7 |
为了方便的查看二进制和十进制和十六进制,可以使用下面的两个方法。
- public class EncodeTest {
-
- public static void main(String[] args) {
- printFormatFromBinary(0b1111);
- printFormatFromHex(0xFF);
- }
-
- /**
- * 格式化打印:0b1111 ->二进制:1111
- * 十进制:15
- * 十六进制: F
- */
- private static void printFormatFromBinary(int binary) {
- System.out.println("二进制:"+Integer.toBinaryString(binary));
- System.out.println("十进制:"+binary);
- System.out.println("十六进制:"+Integer.toHexString(binary).toUpperCase());
- }
-
- /**
- * 格式化打印:0xFF ->二进制:11111111
- * 十进制:255
- * 十六进制: FF
- */
- private static void printFormatFromHex(int hex) {
- System.out.println("二进制:"+Integer.toBinaryString(hex));
- System.out.println("十进制:"+hex);
- System.out.println("十六进制:"+Integer.toHexString(hex).toUpperCase());
- }
-
- }
GBK建立在GB2312的基础上,向下兼容GB2312,也就是说,GB2312编码的字符的二进制表示,在GBK编码里是完全一样的。GBK增加了一万四千多个汉字,共计约21000汉字,其中包括繁体字,GBK同样使用固定的两个字节表示,其中第一个字节范围是1000 0001(十进制129) - 1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126)和1000 0000(十进制128) - 1111 1110(十进制254)。
需要注意的是,第二个字节是从64开始的(64属于byte正数范围,和ASCII的编码重合了),也就是说,第二个字节最高可能为0.那怎么知道它是汉字的一部分还是一个ASCII字符呢?
其实很简单,因为汉字是用固定两个字节表示的,在解析二进制流的时候,如果第一个字节的最高位为1,那么就将下一个字节都进来一起解析为一个汉字,而不是考虑它的最高位,解析完后,跳到第三个字节继续解析。
GB18030向下兼容GBK,增加了五万五千多个字符,共七万六千多个字符,包括了很多少数民族字符,以及中日韩统一字符。用两个字节已经表示不了GB18030中的所有字符,GB18030使用变长编码,有的字符是两个字节,有的是四个字节,在两个字节编码中,字节表示范围与GBK一样。在四个字节编码中,第一个字节的值从1000 0001(十进制129)到1111 1110(十进制254),第二个字节的值从0011 0000(十进制48) 到0011 1001(十进制57),第三个字节的值从1000 0001(十进制129)到1111 1110(十进制254),第四个字节的值从0011 0000(十进制48)到0011 1001(十进制57)。
解析二进制时,如何知道是两个字节还是四个字节表示一个字符呢?很简单,看第二个字节的范围,如果是48到57就是四个字节表示,因为两个字节编码中第二字节都比这个大。所以这样综合说明GB18030兼容GBK,兼容GB2312,兼容ASCII,但是GB18030,GBK,GB2312这三个编码和ISO08859-1是不兼容的。
Big5是针对繁体中文的,广泛用于台湾香港等地。Big5包括1万3千多个繁体字,和GB2312类似,一个字符同样固定使用两个字节表示。在这两个字节中,第一个字节范围是1000 0001(十进制129)到1111 1110(十进制254),第二个字节范围是0100 0000(十进制64) - 0111 1110(十进制126)和1010 0001(十进制161) - 1111 1110(十进制254)。Big5和GB18030,GBK,GB2312不兼容。
兼容:GB2312/GBK/GB18030和ASCII是兼容的。
Windows-1252和ISO-8859-1和ASCII是兼容的。
Big5和ASCII是兼容的。
但是西欧编码和Big5以及GB系列的编码,他们相互之间是不兼容的,也就是同样的码值在三种编码表中显示的内容是不一样的。
兼容
乱码
以上我们介绍了中文和西欧的字符与编码,但是世界上还有很多的国家的字符,每个国家的各种计算机厂商都对自己常用的字符进行编码,在编码的时候基本忽略了别的国家的字符和编码,甚至忽略了同一国家的其他计算机厂商,这样造成的结果就是,出现了太多的编码,且互相不兼容。
世界上所有的字符能不能统一编码呢?可以,Unicode。
Unicode做了一件事,就是给世界上所有字符都分配了一个唯一的数字编号,这个编号范围从0x000000到0x10FFFF,包括110多万。但大部分常用字符都在0x0000到0xFFFF之间,即65536个数字之内。每个字符都有一个Unicode编号,这个编号一般写成16进制,在前面加U+。大部分中文的编号范围在U+4E00到U+9FA5,例如:"贤"的Unicode是U+8D24。
Unicode就做了这么一件事,就是给所有字符分配了唯一数字编号。它并没有规定这个编号怎么对应到二进制表示,这是与上面介绍的其他编码不同的,其他编码都即规定了能表示哪些字符,又规定了每个字符对应的二进制是什么,而是Unicode本身只规定了每个字符的数字编号是多少。
1990年开始研发,1994年正式公布。随着计算机工作能力的增强,Unicode也在面世以来的十多年里得到普及。
Unicode6.3版已发布(2013年11月)。在Unicode联盟网站上可以查看完整的6.3的核心规范。
Unicode定义了大到足以代表人类所有可读字符的字符集。
Unicode其实应该是一个码值表。Unicode的功用是为每一个字符提供一个唯一的数字码,而对数字的存储规则的定义需要依靠UTF-8/UTF-16/UTF-32
UTF-8/UTF-16/UTF-32是通过对Unicode码值进行对应规则转换后,编码保持到内存中。UTF-8/UTF-16都是可变长度的编码方式。
那编号怎么对应到二进制表示呢?有多种方案,主要有UTF-8/UTF-16/UTF-32。
这个最简单,就是字符编号的整数二进制形式,四个字节。
但有个细节,就是字节的排列顺序,如果第一个字节是整数二进制中的最高位,最后一个字节是整数二进制的最低位,那这种字节序就叫"大端"(Big Endian,BE),否则,正好相反的情况,就叫"小端"(Little Endian,LE)。对应的编码方式分别是UTF-32BE和UTF-32LE。比如
Unicode编码 | UTF-32-LE | UTF-32-BE |
0x006C49 | 49 6C 00 00 | 00 00 6C 49 |
0x020C30 | 30 0C 02 00 | 00 02 0C 30 |
注意:之所以有大端和小端两种方式,是因为硬件读写顺序的不同。
大端:数据的高字节保存在内存的低地址中,低字节保存到内存的高地址中,和我们的阅读习惯一致;小端则相反,常用的X86结构是小端模式。采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。
可以看出,每个字符都用四个字节表示,非常浪费空间,实际采用的也比较少。
注意:UTF-32是因为UTF-16编码方式不能表示全部的字符而扩充的编码方式。
在了解UTF-16编码方式之前,先了解一下另外一个概念——"平面"。
在上面的介绍中,提到了Unicode是一本很厚的字典,它将全世界所有的字符定义在一个集合里。那么多的字符不是一次性定义的,而是分区定义。每个区可以存放65536个(2^16)字符,称为一个平面(plane)。目前,一共有17个(2^5)平面(65536*17=1,114,112也就是110多万),也就是说,整个Unicode字符集的大小现在是2^21。
最前面的65536个字符位,称为基本平面(简称BMP),它的码点范围是从0到2^16-1,写成16进制就是从U+0000到U+FFFF。所有最常见的字符都放在这个平面,这是Unicode最先定义和公布的一个平面。剩下的字符都放在辅助平面(简称SMP),码点范围从U+010000到U+10FFFF。
基本了解了平面的概念后,再说回到UTF-16。UTF-16编码介于UTF-32与UTF-8之间,同时结合了定长和变长两种编码方法的特点。它的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节,也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么就是四个字节(U+010000到U+10FFFF)。那么问题来了,当我们遇到两个字节时,到底是把这两个字节当作一个字符还是与后面的两个字节一起当作一个字符呢?
为了将两个字节的UTF-16编码与四个字节的UTF-16编码区分开来,Unicode编码的设计者将0xD800-0xDFFF保留下来,并称为代理区(Surrogate)
辅助平面的字符位共有 2^20 个,因此表示这些字符至少需要20个二进制位。UTF-16将这20个二进制位分成两半,前10位映射在U+D800-U+DBFF,称为高代理位(H),后10位映射在U+DC00-U+DFFF,称为低代理位(L)。这意味着,一个辅助平面的字符,被拆成两个基本平面的字符表示。
D800 - DBFF | High Surrogates | 高位替代 |
DC00 - DFFF | Low Surrogates | 低位替代 |
如果U>=0x10000,我们先计算U'=U-0x10000,然后将U'写成二进制形式:yyyy yyyy yyxx xxxx xxxx,U的UTF-16编码(二进制)就是:110110yyyyyyyyyy110110xxxxxxxxxx。
按照上述规则,Unicode编码0x10000-0x10FFFF的UTF-16编码有四个字节,前两个字节的高6位是110110,后两个字节的高6位是110111,可见,前两个字节的取值范围(二进制)是11011000 00000000到1101101111111111,即0xD800-0xDBFF,后两个字节取值范围(二进制)是1101110000000000到1101111111111111,即0xDC00-0XDFFF。
因此,当我们遇到两个字节,发现它的码点在U+D800到U+DBFF之间,就可以断定,紧跟在后面的两个字节的码点,应该在U+DC00到U+DFFF之间,这四个字节必须放在一起解读。
接下来,以汉字"𠮷"为例,说明UTF-16编码方式是如何工作的。
汉字"𠮷"的unicode码点为 0x20BB7 ,该码点显然超出了基本平面范围(0x0000 - 0xFFFF),因此需要使用四个字节表示。首先用 0x20BB7 - 0x10000计算出超出部分,然后将其用20个二进制位表示(不足前面补0),结果为:0001 000010 1110 110111。接着,将前10位映射到0xD800-0xDBFF之间,后10位映射到0xDC00-0XDFFF即可,0xD800对应的二进制数为 1101100000000000,直接填充后面的10个二进制即可,得到 1101100001000010,转成16进制数为 0xD842.同理可得,低位为0xDFB7。因此得出汉字"𠮷"的UTF-16编码为0xD842 0xDFB7。
和UTF-32一样,UTF-16也有UTF-16LE和UTF-16BE之分,例如:
Unicode编码 | UTF-16LE | UTF-16BE | UTF-32LE | UTF-32BE |
0x006C49 | 49 6C | 6C 49 | 49 6C 00 00 | 00 00 6C 49 |
0x020C30 | 30 DC 43 D8 | D8 43 DC 30 | 30 0C 02 00 | 00 02 0C 30 |
注意:UTF-16常用于系统内部编码,我们常说的"Unicode编码是2个字节"这句话,其实是因为windows系统默认的unicode编码就是UTF-16,在常用基本字符上2个字节的编码方式已经够用导致的误解,其实是可变长的,在没有特殊情况下,常说的Unicode编码可以理解为UTF-16编码,而且是UTF-16BE编码。
UTF-16比UTF-32节省了很多空间,但是任何一个字符都至少需要两个字节表示,对于美国和西欧国家而言,还是很浪费的。
UTF-8就是使用变长字节表示,每个字符使用的字节个数与其Unicode编号的大小有关,编号小的使用的字节就少,编号大的使用的字节就多,使用的字节个数从1到4个不等。
具体来说,各个Unicode编号范围对应的二进制格式如下表所示。
Unicode编码(十六进制) | UTF-8字节流(二进制) |
000000-00007F | 0xxxxxxx |
000080-0007FF | 110xxxxx 10xxxxxx |
000800-00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
010000-10FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
上表中的x表示可以使用的二进制位,而每个字节开头的1或0是固定的。
小于128的(即0x00-0x7F之间的字符),编码与ASCII码一样,最高位为0。其他编号的第一个字节有特殊含义,最高位有几个连续的1表示一共用几个字节表示,而其他字节都以10开头。4字节模板有21个x,即可以容纳21位二进制数字。Unicode的最大码位0x10FFFF也只有21位。
对于一个Unicode编号,具体怎么编码呢?首先,将其看做整数,转化为二进制形式(去掉高位的0),然后将二进制位从右向左依次填入到对应的二进制格式中,填完后,如果对应的二进制格式还没有填的x,则设为0。
例1:"汉"字的Unicode编码是0x6C49。0x6C49在0x0800-0xFFFF之间,使用3字节模板:1110xxxx 10xxxxxx 10xxxxxx。将0x6C49写成二进制是:0110 1100 0100 1001,用这个依次代替模板中的x,得到:1110 0110 1011 0001 1000 1001,即E6 B1 89。
例2:Unicode编码0x20C30在0x010000-0x10FFFF之间,使用4字节模板:11110xxx 10xxxxxx 10xxxxxx 10xxxxxx。将0x20C30写成21位二进制数字(不足21位就在前面补0):000010 0000 1100 0011 0000,用这个依次替代模板中的x,得到:11110000 10100000 10110000 10110000,即F0 A0 B0 B0。
注意:UTF-8和UTF-32、UTF-16不同的地方是UTF-8是兼容ASCII码的,对于大部分中文而言,一个中文字符需要用三个字节表示。UTF-8的优势是网络上数据传输英文字符只需1个字节,可以节省带宽资源。所以当前大部分的网络应用都使用UTF-8编码,因为网络应用的代码编写全部都是使用的英文编写,占据空间小,网络传输速度快。
我们通常会看到这样的编码UTF-8和UTF-8+BOM,那么什么是BOM呢?
比如一个文本软件,在打开一个文件的时候,如何判断这个文件是使用的什么编码呢?该用什么编码进行解码呢?那么就需要通过BOM(Byte Order Mark)来指明了。
Unicode标准建议用BOM(Byte Order Mark)来区分字节序,即在传输字节流前,先传输被作为BOM的字节"零宽无中断空格"。这个字符的编码是FEFF,而反过来的FFEE(UTF-16)和FFFE0000(UTF-32)在Unicode中都是未定义的码位,不应该出现在实际传输中。
UTF编码 | Byte Order Mark(BOM) |
UTF-8 without BOM | 无 |
UTF-8 with BOM | EF BB BF |
UTF-16LE | FF FE |
UTF-16BE | FE FF |
UTF-32LE | FF FE 00 00 |
UTF-32BE | 00 00 FE FF |
注意:UTF-8不需要BOM来表明字节顺序,但可以用BOM来表明文件是UTF-8的编码方式。根据BOM的规则,在一段字节流开始时,如果接收到以下字节,则分别表明该文本的编码。而如果不是以BOM开头,那程序则会以ANSI,也就是系统默认编码读取。
乱码产生的根源一般情况下可以归结为三方面即:编码引起的乱码、解码引起的乱码以及缺少某种字体库引起的乱码(这种情况需要安装对应的字体库),其中大部分乱码问题是由不合适的解码方式造成的。
其中缺少字体,只需要安装对应的字体库即可解决乱码,比如Windows系统在C:\Windows\Fonts 目录下会有安装好的字体库列表。安装字体库比较简单,下载后解压,然后复制到对应系统的Fonts目录下。
解码方式和编码方式不一致的情况,只需要让解码方式和编码方式一致即可让乱码恢复。
GBK编码不支持这几个字符"𠮷""♥""♠",如果在一个GBK编码的文件中,写入"𠮷""♥""♠"这些字符,那么它们就会变成??,?对应的码值是3F,这样的情况下就没有办法恢复,因为"𠮷"的本来的码值变成了2个3F(即两个问号),无论如何也不能恢复过来了。
EBCDIC (Extended Binary Coded Decimal Interchange Code)为国际商用机器公司(IBM)于1963年-64年间推出的字符编码表,根据早期打孔机式的二进化十进数(BCD, Binary Coded Decimal)排列而成。
它的缺点是:英文字母不是连续地排列,中间出现多次断续,为撰写程序的人带来了一些困难。
0x81 to 0x89 ---- ‘a’ to ‘i’
0x91 to 0x99 ---- ‘j’ to ‘r’
0xA2 to 0xA9 ---- ‘s’ to ‘z’
0xC1 to 0xC9 ---- ‘A’ to ‘I’
0xD1 to 0xD9 ---- ‘J’ to ‘R’
0xE2 to 0xE9 ---- ‘S’ to ‘Z’