前言

我们目前主流的计算机的确都是以二进制形式存储数据的。如 00100101(其中的 0 是一个 bit,1 也是一个 bit,这 8 个 bit 就是一个 byte)。通常为了更方便表示二进制数据,也可以转换成 16 进制表示出来,0010 0101 就可以用 16 进制的 0x25 来表示(1byte=2 个 16 进制位 = 8bit=8 个二进制位)。一个字节一共可以用来表示 256 种不同的状态,每一个状态对应一个符号,就是 256 个符号,从 0000000011111111

字符集(character set):规定了某个字符文字对应的二进制数字存放方式(编码)和某串二进制数值代表了哪个文字(解码)的转换关系。

字符编码(character encoding):编码字符集和实际存储数值之间的转换关系,也就是字符转换成二进制数据的规则(要和对应关系区分)。

ASCII 码

ASCII(美国信息交换标准码)是美国国家标准定制的一套基于拉丁字母的电脑编码系统,可表示数字、字母等字符符号。一个 ASCII 码在计算机中由一个字节存储,因此它最多可表示 256 个符号(一个字节为 8 位,2 的 8 次方等于 256)。实事上,标准的 ASCII 编码时只用到了低 7 位 (最高位统一为 0,或者为奇偶校验位),故 ASCII 码可表示的数据一共只有 128 个(2 的 7 次方)。这 128 个字符中,其中 95 个为可显示字符(可打印字符,比如数字、字母、标点符号),还有 33 个如比如“换行” 之类的控制字符(控制字符主要是用来操控已经处理过的文字)。最后附上了 ASCII 码表:

常用字符对应的 [ASCII 编码](https://baike.baidu.com/item/ASCII 编码):

  • 回车,ASCII 码 13(十进制,下同)
  • 换行,ASCII 码 10
  • 空格,ASCII 码 32
  • 数字 0 到 9,ASCII 依次是 48 到 57
  • 大写字母 A 到 Z,ASCII 依次是 65 到 90
  • 小写字母 a 到 z,ASCII 依次是 97 到 122

大小规则总结:

  1. 数字 0~9 比字母要小。如 “7”<“F”
  2. 数字 0 比数字 9 要小,并按 0 到 9 顺序递增。如 “3”<“8”
  3. 字母 A 比字母 Z 要小,并按 A 到 Z 顺序递增。如 “A”<“Z”
  4. 同个字母的大写字母比小写字母要小。如 “A”<“a”

ISO-8859-1 码(Latin-1)

iso8859-1 通常叫做 Latin-1,它和 ascii 编码相似,都属于单字节编码,不同于 ASCII 的是,每个字节中的最高位也参与了编码(如果最高位为 0,它的意义同 ASCII)。正因为如此,iso8859-1 最多能表示的字符范围是 0-255,应用于英文系列。

很明显,iso8859-1 编码表示的字符范围很窄,无法编码中文字符。尽管如此,我们可以先把中文字符按照其它的编码方式编码成二进制数据,然后将编码后的结果再逐字节逐字节的用 iso8859-1 解码。也就是说,中文字符,它没有 iso8859-1 编码,但可以在用其它编码方式编码后的基础上再用 iso8859-1 编码来表示。举个栗子,虽然 “中文” 的“中”这个字不存在 iso8859-1 编码,可以先把它按 gb2312 编码方式编码为 “d6d0” 这个二字节的编码(16 进制),然后将它拆开为两个字节(“d6” 与 “d0”),每个字节都可以看作是一个 iso8859-1 码。

iso8859-1 由于是单字节编码,和计算机最基础的表示单位一致,因此在很多网络传输协议上,默认使用 iso8859-1 编码。网络上传输的数据都是二进制的,服务器从网络 io 流中收到这些数据后,默认把每个字节的数据按 iso8859-1 编码来处理。

实事上,通过网络流传输中文时,客户端可以先把中文字符按照其它的编码方式(比如 GBK 或 UTF-8)转换成字节数据发送到服务器,服务器收到后在不指定编码方式的情况下默认会逐个逐个字节的按照 iso8859-1 编码方式来解码。

随着时间的推移,计算机在世界范围内传播开来中国,日本,韩国等等。这些国家都要设计自己的文字编码表,下面就要讲到中国的 GB 系列

GB2312 码

GB2312 是对 ASCII 的中文扩展,考虑到每个 ASCII 码只用了一个字节的底 7 位(高位为 0),所以每个的 ASCII 码都小于或等于 127(01111111)。

于是规定:** 一个小于 127 的字符的意义与原来相同(为了兼容 ASCII 码),但两个大于 127 的字符连在一起时,就表示一个汉字 **,而且这两个字节中,前面的一个字节(他称之为高字节)从 0xA1 用到 0xF7,后面一个字节(低字节)从 0xA1 到 0xFE,这样我们可以组合出大约 7000 多个简体汉字,这就是 GB2312 编码。

这样,计算机在解码时,可以逐字节逐字节的判断它的是否小于 127,如果小于或等于 127,就按 ASCII 直接解码,如果大于 127,就再往后多读取一个字节,如果后面那个字节也大于 127,就把这两个字节连起来再通过查找 GB2312 编码码表来解码成一个中文字符。由此可见,**GB2312 码是一种变长的编码方式,英文占一个字节(而且小于 127),中文占两个字节(都是大于 127 的字节),而且兼容 ASCII 码 **。

GBK 码

GB2312 码中用两个连续的大于 127 字节的数据来表示一个中文,它能表示的数量刚好也就满足简体中文编码的需要。

后来发现,其实表示一个中文的两个字符中,可以不要求两个字节都大于 127,只要第一个字符大于 127,就可以作为解码中文的开始字符,第二个字符是否大于 127 都不会对计算机解码造成二义性错误(在逐字节解码的过程中,只要读到一个节字大于 127,就直接往后再读一个字节,不判断第二个字节是否大于 127 就直接将这两个字节连到一起然后再通过编码码表来解码)。

这样,在 GB2312 码的基础上,通过不限定第二个字节是否大于 127 的方式扩展出了 GBK 码。GBK 包括了 GB2312 的所有内容,同时又增加了近 20000 个新的汉字(包括繁体字)和符号。

GB18030 码

在 GBK 码的基础上,再进一步扩展,增加了一些连续四个字节的汉字,于是也就有了 GB18030 码,GB18030 码完全兼容 GBK 码,而且增加了少数民族文字综上所述,以上能表示中文的字符编码中,** 可编码的范围从小到大依次是:ASCII < GB2312 < GBK < GB18030,而且它们后者兼容前者,其中 GB2312 和 GB 用两个字节表示中文,GB18030 有些用两个字节表示一个汉字,有些有四个字节表示一个汉字。**

同样,香港台湾那边也对 ASCII 表进行扩充,设计了自己的表叫 BIG5。此外,日本(JIS)、韩国(KSC)也都有对应本国文字的表。上述为了扩充 [ASCII 编码](https://baike.baidu.com/item/ASCII 编码),以于显示本国的语言,不同的国家和地区制定了不同的标准。

这些使用 2 个 [字节](https://baike.baidu.com/item / 字节) 来代表一个字符的各种汉字延伸编码方式,称为 ANSI 编码,又称为 “MBCS(Muilti-Bytes Character Set,多字节字符集)"。在简体中文系统下,ANSI 编码代表 GB2312 编码,在日文操作系统下,ANSI 编码代表 JIS 编码,所以在中文 Windows 下要转码成 gb2312、gbk 只需要把文本保存为 ANSI 编码即可。

不同 ANSI 编码之间互不兼容,当信息在国际间交流时,无法将属于两种语言的文字,存储在同一段 ANSI 编码的文本中。一个很大的缺点是,同一个编码值,在不同的编码体系里代表着不同的字。这样就容易造成混乱(也就是我们常说的乱码)。导致了 unicode 码的诞生。

UNICODE 编码

因为每个国家都搞出像天朝这样一套自己的编码标准,在跨国跨语言使用时存在储多不便。为了统一,ISO(国际标谁化组织)重新搞了一套标准,也就是 UNICODE 编码。

UNICODE 这是最统一的 ** 定长 ** 编码,有双字节编码(UCS-2)和四字节编码(UCS-4,备用)两种。其中 UCS-2 包括英文字母在内,只能表示 65535 个字符,IOS 预备的 UCS-4 方案,可以组合出 21 亿个不同的字符出来(最高位有其他用途)。

其实,英文字母只用一个字节表示就够了,但是其他更大的符号可能需要 3 个字节或者 4 个字节,甚至更多。而 Unicode 统一规定,每个符号用两个或四个字节表示,那么每个英文字母前都必然有二到三个字节是 0,这对于存储来说其实是极大的浪费。

而且它不兼容 iso8859-1 编码,也不兼容 gb2312、gbk、gb18030 等任何编码。不过,相对于 iso8859-1 编码来说,uniocode 编码只是在前面增加了一个 0 字节,比如字母 a 为 “00 61”。

但是 UNICODE 这种定长编码便于计算机处理(注意 GB2312/GBK 不是定长编码),而 unicode 又可以用来表示所有字符,所以在很多软件内部是使用 unicode 编码来处理的,比如 java。

需要注意的是,Unicode 只是一个符号集,它只规定了符号的二进制代码,却没有规定这个二进制代码应该如何存储。于是出现了多种存储方式,也就是说有许多种不同的二进制格式,可以用来表示 unicode。unicode 在很长一段时间内无法推广,直到互联网的出现。

UTF

考虑到 unicode 编码不兼容 iso8859-1 编码,而且容易占用更多的空间:因为对于英文字母,unicode 也需要两个字节来表示。所以 unicode 不便于传输和存储。因此而产生了 utf 编码,utf 编码兼容 iso8859-1 编码,同时也可以用来表示所有语言的字符,不过,utf 编码是不定长编码,每一个字符的长度从 1-6 个字节不等。另外,utf 编码自带简单的校验功能。

**UTF-8 是 Unicode 的实现方式之一 **,一般来讲,对于 UTF-8,英文字母都是用一个字节表示,而汉字使用三个字节。另外,还有 UTF-16(字符用两个字节或四个字节表示),UTF-32(字符用四个字节表示)。

顾名思义,UTF-8 就是每次 8 个位传输数据,而 UTF-16 就是每次 16 个位。UTF-8 就是在互联网上使用最广的一种 unicode 的实现方式,这是为传输而设计的编码,并使编码无国界,这样就可以显示全世界上所有文化的字符了。

UTF-8 的编码规则很简单,只有二条:

  1. 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的。
  2. 对于 n 字节的符号(n>1),第一个字节的前 n 位都设为 1,第 n+1 位设为 0,后面字节的前两位一律设为 10。剩下的没有提及的二进制位,全部为这个符号的 unicode 码。

综上所述,UTF-8 最大的一个特点,就是它是一种变长的编码方式。它可以使用 1~4 个字节表示一个符号,根据不同的符号而变化字节长度。由此可见 UTF-8 编码的文件比 GB2312 更占空间大。

参考

十分钟搞清字符集和字符编码