Skip to content

字符编码

概述

  我们知道计算机的内存或磁盘里存储的数据都是二进制的,那各种字符对应的二进制应该是什么样子呢?计算机一开始要存的字符无非就是英文字母、数字、和一些特殊符号(+-&/?!.等)。所以只需要把这些字符编个号,然后存这些编号就行了。

  上面说的“编号”准确的叫法应该是 代码点 (code point),而记录了所有我们需要编码的字符和它的代码点的对照表就是所谓的字符集 (Character set),不同的字符集收录的字符可能不同,即使是同一个字符,在不同的字符集里也可能有不同的代码点。

  不管怎么说每个字符集都至少有一种把代码点和二进制进行互相转换的规则,这种规则就叫做字符编码 (Character encoding)。一个字符集可能存在多种字符编码方式,所以即使是同一个字符集里的同一字符(同一个代码点),按照不同的字符编码转换成二进制,结果也可能不一样。比如一个“Unicode 字符集”里的字符,如果采用 “UTF-32” 编码转化成二进制,得到的一定是一段 4 字节大小的二进制,但用 “UTF-16” 编码得到的二进制大小可能是 2 字节 也可能是 4 字节,这是因为 UTF-16 编码的 代码单元 code unit 是 2 字节,如果遇到代码点大于 65535 的字符,一个代码单元显然不足以表示,只能采用两个代码单元表示,就会占用 4 字节的空间。下面是常见的字符集和不同的编码方式。

ASCII 字符集

ASCⅡ表 以及 扩展ASCII码表

计算机中的数据都是 0 和 1,不管是在 RAM 还是 ROM 中。我们常用的十进制的数字会转化成二进制保存,比如 26 会转化成二进制数 11010。

那么英文字符(英文字母与英文符号)怎么存储呢?

解决方法是一个约定好的在字符和数字间的映射,最流行的是 ASCII。

它是一个很小的字符集,但也是最常见的字符集。它收录了 128 个字符,并用 0-127 的代码点表示它们,他只有一种编码方式,也很简单,就是直接写出代码点的二进制就行了,所以一个 ASCII 字符只占用一个字节,字节的第一位永远是 0。比如数字 "0" 的代码点是 48,那么它编码后的的二进制表示就是 0b00110000 (0b 表示二进制)、解码时如果读取到的字节是 00110000,我们也就知道了这个字节表示的是代码点为 48 的字符,即字符 "0"。不过我们在书写 1 个字节时,通常为了方便阅读,会写成 2 个 十六进制数,比如刚才那个字节的十六进制表示就是 0x30(0x 表示十六进制)。同样的,像大写字母 A ,它的代码点是 65 ,编码后的字节就是 0x41、小写字母 a ,它的代码点是 97,字节就是 0x61了。

ASCII 字符集中除了大小写英文字母和数字外,其中 0 - 31 和 127 的代码点是用来表示一些控制字符的。除去字母、数字、控制字符,剩余的代码点就给了一些特殊符号,比如 +-&/?!. 等。ASCII 编码是一种非常节省空间的编码方式,任何一个 ASCII 字符都只需要一个字节就足以表示了。

Hello 转换为为二进制的过程

StringHello
ASCⅡ value72101108108111
Binary(二进制)0100100001100101011011000110110001101111

最后把这些二进制数字拼接起来存储即可。每一个字符都会转换成一个 8 位的二进制数字,称为 1 字节(8 bits,1 byte)。每个字符占 1 个字节。

扩展ASCII码

扩展ASCII码新增了 128-255 字符及对应的码点。主要用于英文、 爱尔兰语、意大利语、挪威语、葡萄牙语、西班牙语、瑞典语以及德语、芬兰语、冰岛语、法语、荷兰语(除了IJ字符)、斯洛文尼亚语(除了č字符 ) 等西方文字版本 Microsoft Windows 的预设编码,为 Windows 代码页之一 。

GB字符编码

GB2312

GB2312编码表

GB2312 标准由中国国家标准总局 1980 年发布,GB 即国标,共收录 6763 个汉字,其中一级汉字 3755 个,二级汉字 3008 个。

  • GB 2312 的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆 99.75% 的使用频率。

  • 对于人名、古汉语等方面出现的罕用字,GB 2312 不能处理,这导致了后面的 GBK 及 GB 18030 汉字字符集的出现。

  • GB 2312 收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的 682 个全角字符。

  • GB 2312 兼容 ASCII 码(0 - 127),之后对任意一个图形字符都采用两个字节表示,高位字节和低位字节都大于 127

  • GB 2312 字符集分成 94 个区,每区有 94 个位,分别对应第一字节和第二字节,这种表示方式也称为区位码。

01-09 区为特殊符号

10-15 区为用户自定义符号区(未编码)

16-55 区为一级汉字,按拼音排序,共 3755 个

56-87 区为二级汉字,按部首/笔画排序,共 3008 个

88-94 区为用户自定义汉字区(未编码)

注意

为了兼容 ASCII。 GB2312 是双字节编码,为了与 ASCII 码区分开,字节的第 8 位必须是 1,所以至少要从 0x80 (128, 1000 0000) 开始,但是根据上面的规定,0x80 - 0x9f 要留给控制块,所以只能从 0xA0 开始。

在计算机中的存储方式

js
//“你”  存在于36区 67, 汉字对应两个字节,第一个字节对应的是36,第二字节对应的是67
// 36转为16进制 = 24
// 67转为16进制 = 43
// 从 0xA0 开始
// 0xA0 + 24 = c4
// 0xA0 + 43 = e3
const gbkBuf = new Uint8Array([0xc4,0xe3]);
const text = new TextDecoder('gbk').decode(gbkBuf); // 你
//“你”  存在于36区 67, 汉字对应两个字节,第一个字节对应的是36,第二字节对应的是67
// 36转为16进制 = 24
// 67转为16进制 = 43
// 从 0xA0 开始
// 0xA0 + 24 = c4
// 0xA0 + 43 = e3
const gbkBuf = new Uint8Array([0xc4,0xe3]);
const text = new TextDecoder('gbk').decode(gbkBuf); // 你

GBK

GBK,Chinese Internal Code Specification,即汉字内码扩展规范,K 为汉语拼音 Kuo Zhan(扩展)中的“扩”字的声母,于 1995 年 12 月发布的汉字编码国家标准。

GBK 共收录 21886 个汉字和图形字符

  • GB2312 中的全部汉字( 6763 个)、非汉字符号
  • Big5 中的全部汉字
  • 与 ISO 10646 相应的国家标准 GB 13000 中的其他 CJK(中日韩) 汉字
  • 其他汉字、部首、符号,共计 984 个

GBK 采用双字节表示,总体编码范围为 8140-FEFE 之间,首字节在 81-FE 之间,尾字节在 40-FE 之间,不再规定低位字节大于 127,剔除 XX7F 一条线

GBK 编码区分三部分:

(1)、汉字区

GBK/2:0XBOA1-F7FE, 收录 GB 2312 汉字 6763 个,按原序排列

GBK/3:0X8140-AOFE,收录 CJK 汉字 6080 个

GBK/4:0XAA40-FEAO,收录 CJK 汉字和增补的汉字 8160 个


(2)、图形符号区

GBK/1:0XA1A1-A9FE,除 GB 2312 的符号外,还增补了其它符号

GBK/5:0XA840-A9AO,扩除非汉字区


(3)、用户自定义区

GBK 区域中的空白区,用户可以自己定义字符

GB18030

GB18030,国家标准 GB 18030-2005,是中国目前最新的内码字集,于 2000 年 3 月发布的汉字编码国家标准,与 GB 2312-1980 和 GBK 兼容,共收录汉字 70244 个:

  • 与 UTF-8 相同,采用多字节编码,每个字可以由 1 个、2 个或 4 个字节组成

  • 编码空间庞大,最多可定义 161 万个字符

  • 支持中国国内少数民族的文字,不需要动用造字区

  • 汉字收录范围包含繁体汉字以及日韩汉字

GB 18030 编码是一二四字节变长编码:

  • 单字节,其值从 0 到 0x7F,与 ASCII 码兼容

  • 双字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x40 到 0xFE(不包括 0x7F),与 GBK 标准兼容

  • 四字节,第一个字节的值从 0x81 到 0xFE,第二个字节的值从 0x30 到 0x39,第三个字节从 0x81 到 0xFE,第四个字节从 0x30 到 0x39。

字符处理软件在处理文本时,从左往右依次扫描每个字节:

如果遇到的字节的最高位是 0,那么就会断定该字符只占用了一个字节

如果遇到的字节的最高位是 1,那么该字符可能占用了两个字节,也可能占用了四个字节,不能妄下断论,所以还要继续往后扫描:

如果第二个字节的高位有两个连续的 0,那么就会断定该字符占用了四个字节 如果第二个字节的高位没有连续的 0,那么就会断定该字符占用了两个字节

当字符占用两个或者四个字节时,GB18030 编码要检测两次,处理效率比 GB2312 和 GBK 都低。

Unicode 字符集

Unicode编码概述

Unicode编码是一种字符编码标准,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。

面对各个国家地区的各自需求,他们都搞出了各种花里胡哨的字符集和字符编码方式,但互联网的发展要求大家必须得有一个统一的字符集和编码方式,不然怎么在一个网页里同时展示中文、英文、日文、韩文、阿拉伯文等。总不能来来回回切换字符集和字符编码方式吧?于是Unicode 标准就出来了。这个字符集的代码点足够多,有一百多万个(从 0x000000 到 0x10FFFF),这么一来即使全世界所有语言的字符都放进来也还绰绰有余,它包含了100 多种语言中的超过 10 万个独特字符,甚至放了很多 emoji 😂,既然可以表示这么多字符,那岂不是每个字符也需要更大的空间,不然岂不是会重复?确实如此,需要 4 个字节来表示。但 Unicode 提供了多种字符编码方式,有些编码方式可以帮我们节省空间,比如大家最常听到的 UTF-8,接下来我们就一起了解一下 Unicode 最常见的三种编码方式:UTF-32 编码、UTF-16 编码、UTF-8 编码

字符集: 为每一个 字符 分配一个唯一的数字 ID(学名为码位/码点/Code Point)。

编码规则: 将 码位 转换为 字节序列 的规则。

UTF-32 与 UCS-4

在 Unicode 与 ISO 10646 合并之前,ISO 10646 标准为“通用字符集”(UCS:Unicode Character Set)定义了一种 31 位的编码形式(即 UCS-4),其编码固定占用 4 个字节,编码空间为 0x00000000~0x7FFFFFFF(可以编码 20 多亿个字符)。 UCS-4 有 20 多亿个编码空间,但实际使用范围并不超过 0x10FFFF,并且为了兼容 Unicode 标准,ISO 也承诺将不会为超出 0x10FFFF 的 UCS-4 编码赋值。由此 UTF-32 编码被提出来了,它的编码值与 UCS-4 相同,只不过其编码空间被限定在了 0~0x10FFFF 之间。因此也可以说:UTF-32 是 UCS-4 的一个子集。

UTF-16 与 UCS-2

除了 UCS-4,ISO 10646 标准为“通用字符集”(UCS)定义了一种 16 位的编码形式(即 UCS-2),其编码固定占用 2 个字节,它包含 65536 个编码空间(可以为全世界最常用的 63K 字符编码,为了兼容 Unicode,0xD800-0xDFFF 之间的码位未使用)。例:“汉”的 UCS-2 编码为 6C49。 但两个字节并不足以真正地“一统江湖”(a fixed-width 2-byte encoding could not encode enough characters to be truly universal),于是 UTF-16 诞生了,与 UCS-2 一样,它使用两个字节为全世界最常用的 63K 字符编码,不同的是,它使用 4 个字节对不常用的字符进行编码。UTF-16 属于变长编码。

UTF-8

从前述内容可以看出:无论是 UTF-16/32 还是 UCS-2/4,一个字符都需要多个字节来编码,这对那些英语国家来说浪费空间!于是UTF-8 产生了。在 UTF-8 编码中,ASCII 码中的字符还是 ASCII 码的值,只需要一个字节表示,其余的字符需要 2 字节、3 字节或 4 字节来表示。

UTF-8 的编码规则:

  • (1) 对于 ASCII 码中的符号,使用单字节编码,其编码值与 ASCII 值相同。其中 ASCII 值的范围为 0~0x7F,所有编码的二进制值中第一位为 0(这个正好可以用来区分单字节编码和多字节编码)。

  • (2) 其它字符用多个字节来编码(假设用 N 个字节),多字节编码需满足:第一个字节的前 N 位都为 1,第 N+1 位为 0,后面 N-1 个字节的前两位都为 10,这 N 个字节中其余位全部用来存储 Unicode 中的码位值。

字节数Unicode十进制UTF-8 编码(8 bits,1-4 byte)
1000000-00007F0-1270xxxxxxx
2000080-0007FF128-2047110xxxxx 10xxxxxx
3000800-00FFFF2048-655351110xxxx 10xxxxxx 10xxxxxx
4010000-10FFFF65536-111411111110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UTF-8 转码

  1. "你" 对应的Unicode码为: 0x0000 4F60

  2. 0x0000 4F60 转为 二进制结果为: 0000 0000 0000 0000 0100 1111 0110 0000 (32位)

  3. "你" 对应第三区 UTF-8 编码格式为: 1110xxxx 10xxxxxx 10xxxxxx

  4. 第2步的结果与第3步的 UTF-8 编码格式对应上(右往左): 11100100 10111101 10100000

  5. 将三个字节转为二进制 11100100 10111101 10100000 ==> E4 BD A0

  6. "你" 对应的UTF-8编码为: E4 BD A0