首页 归档 关于 learn love 工具

字符集和字符编码

字符集就是字符的集合,如常见的 ASCII字符集,GB2312字符集,Unicode字符集等。这些不同字符集之间最大的区别是所包含的字符数量的不同。

字符编码则代表字符集的实际编码规则,是用于计算机解析字符的,如 GB2312,GBK,UTF-8 等。字符编码的本质就是如何使用二进制字节来表示字符的问题。

字符集和编码是一对多的关系,同一字符集可能有多种字符编码,如Unicode字符集就有 UTF-8,UTF-16 等。

在前端开发中,Javascript程序是使用Unicode字符集,Javascript源码文本通常是基于UTF-8编码。
但js代码中的字符串类型是UTF-16编码的,这也是为什么会碰到api接口返回字符串在前端出现乱码,因为多数服务都使用utf-8编码,前后编码方式不一致。

说起字符集的发展历程,可以总结为一句话:几乎都是对ASCII字符集的扩展。

ASCII

我们知道,计算机是使用二进制来处理信息的。
其中,每一个二进制位(bit)有 0和1 两种状态。一个字节(byte)则有8个二进制位,可以有256种状态。

而ASCII就是基于拉丁字母、主要用于显示英文的一种单字节字符集,它的编码和字符是一一对应的,因为它就是使用一个字节8个二进制位来表示,不会超过256个字符。

标准的ASCII字符总计有128个字符(2^7),其中前面32个控制字符,后面96个是可打印字符,包括常用的大小写字母数字标点符号等。因为只占用了一个字节的后7位,那字节的最高位一般设置为0。

'a'.charCodeAt() // 97
'A'.charCodeAt() // 65
'9'.charCodeAt() // 57
'.'.charCodeAt() // 46

如上,每个字符会对应一个编码(使用数字标识),总共会从0-128。完整的ASCII码表,网上很容易找到。

通过ASCII码表,我们发现,小写字母并没有和大写字母挨着排序?这是为了方便大小写之间的转换, A 排在 65(64 + 1) 位,而 a 排在 97(64 + 32 + 1) 位。

65 ^ 32 = 97
// A ^ 32 = a

字符集的发展历史

ASCII是几乎所有字符集的基础。

标准的ASCII码最多只能标识128个字符,欧美国家可以很好的使用,但其他国家的字符变多,自然就不够用了。
这个时候,最高位就开始被惦记上,通过扩展ASCII码的最高位,又能满足用于特殊符号的一些国家的需求,这种就是扩展ASCII码。

但是亚非拉更多非拉丁语系的国家,字符成千上万,只能使用新的方式。
如中文,就又进行了扩展,小于127的字符的意义与标准ASCII码相同,当需要标识汉字时,使用2个字节,每个字节都大于127。这种多字节字符集即GB2312,后续因为不断的扩展,如繁体字和各种符号,甚至少数民族的语言符号等等,又使用了包括GBK等不同字符集。
因此,很多国家都制定了自己的编码字符集,基本都是在ASCII的基础上进行的。

各字符集虽然都能够兼容标准ASCII码,但在使用交流上的不便是显而易见的,乱码也是随处可见。为了解决这种各自为战的问题,Unicode字符集就诞生了。

Unicode

Unicode是国际组织制定的,用于收纳世界上所有文字和符号的字符集方案。
前128个字符同ASCII一样,进行扩充后,使用数字0-0x10FFFF来映射这些字符,最多可以有1114112个字符。目前仍然只使用了其中的一小部分。

Unicode 编码字符集旨在收集全球所有的字符,为每个字符分配唯一的字符编号即代码点(Code Point),用 U+紧跟着十六进制数表示。所有字符按照使用上的频繁度划分为 17 个平面(编号为 0-16),即基本的多语言平面和增补平面。基本的多语言平面(英文为 Basic Multilingual Plane,简称 BMP)又称平面 0,收集了使用最广泛的字符,代码点从 U+0000 到 U+FFFF,每个平面有 2^16=65536 个码点;增补平面从平面 1~16,分为增补多语言平面(平面 1)、增补象形平面(平面 2)、保留平面(平面 3~13)、增补专用平面等,每个增补平面也有 2^16=65536 个码点。所以 17 个平面总计有 17 × 65,536 = 1,114,112 个码点。下图 是 Unicode 平面分布图,以及 Unicode 各个平面码点空间


Unicode只是一个字符集,定义了字符与数字的映射关系,但对于计算机中如何存储,没有做任何规定。由于字符数量之大,码点的范围很宽,排在前面的码点,可能用1个字节就能表示,而码点较大的,可能需要2个字节,3个字节,4个字节才能表示。那么计算机如何确定是将1个字节解释为一个字符呢,还是将2个字节连在一起解释为一个字符呢?还是3个,4个字节连在一起为一个字符。于是出现了一些解决方案:

  • 取最大的,将所有字符都存储为4个字节(假设4个字节已经足够囊括所有码点),这样计算机固定以4个字节为单位来解释字符编码。但这样会产生极大的空间浪费,对于一篇纯英文文档,会浪费3倍的空间。
  • 存储表示字节数的信息,让计算机知道该字符是以几个字节来存储的,如UTF-8编码。

Unicode字符集可以有不同的编码方式,如UTF-8,UTF-16,UTF-32,这里UTF指的是Unicode Transformation Format,即Unicode转换格式,即将Unicode编码空间中每个字符对应的码点,与字节顺序进行一一映射。

码元

码元(Code Unit)可以理解为对码点进行编码时的最小基本单元,码元是一个整体。而字符编码的作用就是将Unicode码点转换成码元序列。
Unicode常用的编码方式有 UTF-8 、UTF-16 和 UTF-32,UTF是Unicode TransferFormat的缩写。
UTF-8是8位的单字节码元,UTF-16是16位的双字节码元,UTF-32是32位的四字节码元。

编码方式 码元 编码后字节数
UTF-8 8位 1-4字节
UTF-16 16位 2字节或者4字节
UTF-32 32位 4字节

另外,为什么总看到使用十六进制数据来表示如码点等各种数据呢?
因为,两位的十六进制正好等于一个字节8位,0xff = 0b11111111。

UTF 字节序

最小编码单元是多字节才会有字节序的问题存在,UTF-8 最小编码单元是一字节,所以 它是没有字节序的问题,UTF-16 最小编码单元是 2 个字节,在解析一个 UTF-16 字符之前,需要知道每个编码单元的字节序

比如:前面提到过,"中" 字的 Unicode 码是 4E2D, "?" 字符的 Unicode 码是 2D4E, 当我们收到一个 UTF-16 字节流 4E2D 时,计算机如何识别它表示的是字符 "中" 还是 字符 "?" 呢 ?

所以,对于多字节的编码单元,需要有一个标记显式的告诉计算机,按照什么样的顺序解析字符,也就是字节序,字节序分为 大端字节序 和 小端字节序

小端字节序简写为 LE( Little-Endian ), 表示 低位字节在前,高位字节在后, 高位字节保存在内存的高地址端,而低位字节保存在内存的低地址端

大端字节序简写为 BE( Big-Endian ), 表示 高位字节在前,低位字节在后,高位字节保存在内存的低地址端,低位字节保存在在内存的高地址端

下面以 0x4E2D 为例来说明大端和小端,具体参见下图:

数据是从高位字节到低位字节显示的,这也更符合人们阅读数据的习惯,而内存地址是从低地址向高地址增加

所以,字符0x4E2D 数据的高位字节是 4E,低位字节是 2D

按照大端字节序的高位字节保存内存低地址端的规则,4E 保存到低内存地址 0x10001 上,2D 则保存到高内存地址 0x10002 上

对于小端字节序,则正好相反,数据的高位字节保存到内存的高地址端,低位字节保存到内存低地址端的,所以 4E 保存到高内存地址 0x10002 上,2D 则保存到低内存地址 0x10001 上

BOM

BOM 是 byte-order mark 的缩写,是 "字节序标记" 的意思, 它常被用来当做标识文件是以 UTF-8、UTF-16 或 UTF-32 编码的标记 在 Unicode 编码中有一个叫做 "零宽度非换行空格" 的字符 ( ZERO WIDTH NO-BREAK SPACE ), 用字符 FEFF 来表示 。对于 UTF-16 ,如果接收到以 FEFF 开头的字节流, 就表明是大端字节序,如果接收到 FFFE, 就表明字节流 是小端字节序。

UTF-8 没有字节序问题,上述字符只是用来标识它是 UTF-8 文件,而不是用来说明字节顺序的。"零宽度非换行空格" 字符 的 UTF-8 编码是 EF BB BF, 所以如果接收到以 EF BB BF 开头的字节流,就知道这是UTF-8 文件 , 下面的表格列出了不同 UTF 格式的固定文件头

UTF编码 固定文件头
UTF-8 EF BB BF
UTF-16LE FF FE
UTF-16BE FE FF
UTF-32LE FF FE 00 00
UTF-32BE 00 00 FE FF

根据上面的 固定文件头,下面列出了 "中" 字在文件中的存储 ( 包含文件头 )

编码 固定文件头
Unicode 编码 0X004E2D
UTF-8 EF BB BF 4E 2D
UTF-16BE FE FF 4E 2D
UTF-16LE FF FE 2D 4E
UTF-32BE 00 00 FE FF 00 00 4E 2D
UTF-32LE FF FE 00 00 2D 4E 00 00

MySQL 中的 utf8 和 utf8mb4

MySQL 中的 "utf8" 实际上不是真正的 UTF-8, "utf8" 只支持每个字符最多 3 个字节, 对于超过 3 个字节的字符就会出错, 而真正的 UTF-8 至少要支持 4 个字节。MySQL 中的 "utf8mb4" 才是真正的 UTF-8,下面以 test 表为例来说明, 表结构如下:

mysql> show create table test\G 
*************************** 1. row *************************** 
       Table: test 
Create Table: CREATE TABLE `test` ( 
  `name` char(32) NOT NULL 
) ENGINE=InnoDB DEFAULT CHARSET=utf8 

向 test 表分别插入 "中" 字 和 Unicode 码为 0x10A6F 的字符,这个字符需要从 https://unicode-table.com/cn/10A6F/ 直接复制到 MySQL 控制台上,手工输入会无效,具体的执行结果如下图:

原文链接

https://www.cnblogs.com/jimojianghu/p/16205678.html
https://blog.51cto.com/u_15445690/4720328
https://www.51cto.com/article/661981.html