概述
对字符编码情况进行梳理。
二进制
二进制:由 0 或 1 的比特位组成的编码。
bit:比特位,由电平高低衍生的计算机的 0 与 1。
- 计算机中,所有数据,不论文字、图片、视频、音频,本质上都是二进制
0101存储的。
ASCII 编码
ASCII(American standard code for information interchange):美国信息交换标准代码,以 8 位 bit 表示不同的字符,最左边为校验位,所以为 $2^7 =128$ 个不同的字符,表大小英文字母、数字、符号、及各种控制字符。
- 美国人用 8 个可以开合的晶体管(0 或 1)来组成不同的状态,为一个字节(Byte),即 8 个比特位构成一个字节,有 $2^8$ 即 256 种不同的状态,如
A--->00010001占一个字节, 一个字节的最大整数是二进制 11111111=十进制 255。这些字节组合起来的东西,能实现一些功能,就叫它“计算机”。 - 当时只有 127 个字符,包括大小写英文字母、数字和一些符号,这个编码表即 ASCII 编码,比如大写字母 A 的编码是 00010001 ,即 65。
- 前 32 位为非打印控制字符,如
00x10, 终端换行,0x07, 终端嘟嘟叫; - 后面的为打印字符:如空格、标点符号、数字、大小写字母分别用连续的字节状态表示,
- 传到别的国家,127 个字符不够,就添加到 255 位,第 128-255 位称为“扩展字符集”。
- 前 32 位为非打印控制字符,如
- ANSI 是 ASCII 的扩展,除英文外还可以表示拉丁文。
GB2312 编码
GB2312:ASCII 编码 + 中文,小于 127 的字符与 ASCII 的意义相同,将两个大于 127 的字符连在一起,表示一个汉字,前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE,这样我们就可以组合出大约 7000 多个简体汉字了;这种汉字方案叫做 “GB2312”。
- 半角字符:原先在 127 号 ASCII 中的一个字节长的字符。
- 全角字符:新来的包括对原来的 127 号字符重新编码的两个字节长的称为“全角”字符。
GBK 和 GB18030 编码
GBK:汉字太多,GB2312 不够用,于是规定:只要第一个字节是大于 127 就固定表示这是一个汉字的开始,不管后面跟的是不是扩展字符集里的内容。结果扩展之后的编码方案被称为 GBK 标准,GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号。GB18030:少数民族觉得 GBK 也不够用,再扩展,增加了几千个新的少数民族的字,成为了 GB18030。DBCS:Double Byte Charecter Set: 双字节字符集:中国的程序员通称这些两个字节表示一个字符的标准为 DBCS 系列标准。其最大特点是两字节长的汉字字符与一字节长的英文字符在同一套编码方案里。字串中每个字节的值,若大于 127,则为双字节字符集中的字符。即“一个汉字长等于两个英文字符长”。
万国码 -Unicode
各个国家都要用计算机了,日文编码:Shif_JIS,韩文编码:Euc-kr…现有的编码不够用了,为解决各国的自编码的信息交互问题,ISO(国际标准化组织)废了所有的地区性编码方案,重新搞一个包括了地球上所有文字、字母和符号的编码,即 “Universal Multiple-Octet Coded Character Set”,简称 UCS, 俗称 “UNICODE”。
Unicode:万国码,用两个字节,即 16 位来表示一个字符,有 65535 种不同的字符,足以覆盖世界上所有符号(包括甲骨文)
- 构造:
- 对 ASCII 的半角字符,UNICODE 保持其原编码不变,只是将长度由原来的 8 位变为 16 位;
- 对于 ascii 里的那些“半角”字符,UNICODE 包持其原编码不变,只是将其长度由原来的 8 位扩展为 16 位,而其他文化和语言的字符则全部重新统一编码。
- 由于 “ 半角 “ 英文符号只需要用到低 8 位,所以其高 8 位永远是 0,因此这种大气的方案在保存英文文本时会多浪费一倍的空间。
- 评价:
- UNICODE 没有考虑与任何一种现有编码方案兼容(除了 ASCII),即如 GBK 与 UNICODE 在汉字上的编排完全不一样,只能通过一一对应的速查表才能进行转换。
- 如前所述,UNICODE 是用两个字节来表示为一个字符,他总共可以组合出 65535 不同的字符,这大概已经可以覆盖世界上所有文化的符号。如果还不够也没有关系,ISO 已经准备了 UCS-4 方案,说简单了就是四个字节来表示一个字符,这样我们就可以组合出 21 亿个不同的字符出来(最高位有其他用途),这大概可以用到银河联邦成立那一天吧!
- 在数据库中,有 n 前缀的字串类型就是 UNICODE 类型,此时,用两个字节表示一个字符,无论它是汉字还是英文字母,还是其他什么。即 “abc Shawn “,在无 n 前缀的数据类型中,为 5 个字符长度,有 n 前缀的数据类型中,为 4 个字符长度。
UTF-8
UTF-8:8-bit Unicode Transfer Format:可变长编码。面向传输,UTF-8,一次 8 个位传输数据,UTF-16 每次 16 个位
Unicode 编码时代,乱码没有了,但如果都是英文,用 Unicode 比 ASCII 多一倍的空间,如 A 从 ASCII 的 00010001 变成了 Unicode 的 00000000 00010001,这太浪费了。所以出现了 UTF 编码。
unicode 分占 4 个字节的 utf-32,2 个字节的 utf-16,1-4 个字节的 utf-8,其中 utf-16 是现在最常用的 unicode 版本, 不过在文件里存的还是 utf-8,因为 utf8 省空间。
根据不同的符号变化字节长度,当字符在 ASCII 码的范围内时,使用一个字节表示,所以它是兼容 ASCII 编码的。
UTF-8 编码把一个 Unicode 字符根据不同的数字大小编码成 1-6 个字节,常用的英文字母被编码成 1 个字节,汉字通常是 3 个字节,只有很生僻的字符才会被编码成 4-6 个字节。如果你要传输的文本包含大量英文字符,用 UTF-8 编码就能节省空间:
| 字符 | ASCII | Unicode | UTF-8 |
|---|---|---|---|
| A | 1000001 | 00000000 01000001 | 1000001 |
| 中 | x | 01001110 00101101 | 11100100 10111000 10101101 |
- Unicode 是规范,是内存编码表示方案
- UTF:是实现,是保存和传输 Unicode 的方案,
用记事本编辑的时候,从文件读取的 UTF-8 字符被转换为 Unicode 字符到内存里,编辑完成后,保存的时候再把 Unicode 转换为 UTF-8 保存到文件:
unicode 是离用户更近的数据,bytes 是离计算机更近的数据。
ASCII 编码和 Unicode 编码的区别:ASCII 编码是 1 个字节,而 Unicode 编码通常是 2 个字节。
乱码/问号/方块
BOM
BOM:Byte-Order Mark:字节序标记,是 Unicode 码点 U+FEFF。它被定义来放在一个 UTF-16 文件的开头,如果字节序列是 FEFF 那么这个文件就是大端序,如果字节序列是 FFFE 那么这个文件就是小端序。
锟斤拷
在将一些国家语言编码体系,比如 GB、BIG-5、EUC-JP 等,转换为 Unicode 的过程中,多少有一些字符是不在 Unicode 中的(比如一些偏旁部首在 Unicode 里是后来才收录的),甚至它本身在原来的编码体系里面就是非法字符的情况。
Unicode 规定了 U+FFFD 当作一个占位符用来表示这些字符,用 UTF-8 编码它就是 EF BF BD,连续多个这样的字节序列出现就成了 EF BF BD EF BF BD。如果是一个 UTF-8 的解析程序还好,而如果用一个 GB 的解析程序去打开,一个汉字 2 字节,就成了“锟斤拷”。
烫烫烫、屯屯屯
- VC 在 Debug 模式下,会将未初始化的栈内存全部填写成
0xCC,未初始化的堆内存填成0xCD,这样便能让开发者一眼看出开了内存没初始化。而在 GBK 编码下,CC CC 就是“烫”,CD CD 就是“屯”。
附 1 ASCII 编码
1 | Bin Dec Hex 缩写/字符 解释 |
