参考链接
背景
ASCII编码
ASCII ((American Standard Code for Information Interchange): 美国信息交换标准代码)是基于拉丁字母的一套电脑编码系统,主要用于显示现代英语和其他西欧语言。它是最通用的信息交换标准,并等同于国际标准ISO/IEC 646。ASCII第一次以规范标准的类型发表是在1967年,最后一次更新则是在1986年,到目前为止共定义了128个字符 [1] 。
随着计算机的普及,不同国家和地区有出现了很多字符编码,如大陆的GB2312、港台的BIG5,日本的Shift JIS等。不同的字符编码往往会导致不同国家与地区之间交流变得十分困难,经常出现乱码的情况。
于是,为了统一所有国家和地区的字符编码,Unicode应运而生。
Unicode介绍
Unicode一般称为统一码,也叫万国码、单一码是计算机科学领域里的一项业界标准,包括字符集、编码方案等。Unicode 是为了解决传统的字符编码方案的局限而产生的,它为每种语言中的每个字符设定了统一并且唯一的二进制编码,以满足跨语言、跨平台进行文本转换、处理的要求。1990年开始研发,1994年正式发布1.0版本,2020年发布13.0版本。
Unicode 字符集的编码范围是 0x0000 - 0x10FFFF , 可以容纳一百多万个字符, 每个字符都有一个独一无二的编码,也即每个字符都有一个二进制数值和它对应,这里的二进制数值也叫码点 , 比如:汉字 “中” 的 码点是 0x4E2D, 大写字母 A 的码点是 0x41, 具体字符对应的 Unicode 编码可以查询 Unicode字符编码表。如下示例:
字符集和字符编码
字符集是很多个字符的集合,例如 GB2312 是简体中文的字符集,它收录了六千多个常用的简体汉字及一些符号,数字,拼音等字符。
字符编码是 字符集的一种实现方式,把字符集中的字符映射为特定的字节或字节序列,它是一种规则。
比如:Unicode 只是字符集,UTF-8、UTF-16、UTF-32 才是真正的字符编码规则。
Unicode字符存储
Unicode 是一个符号集, 它只规定了每个符号的二进制值,但是符号具体如何存储它并没有规定。
Unicode 字符集的编码范围是 0x0000 - 0x10FFFF,因此需要 1 到 3 个字节来表示(?此处存疑,还有极少量字符需要用4个字节来表示,如"𪚥")。
如果所有字符都用三个字节表示,那么对于那些一个字节就能表示的字符来说,有两个字节是无意义的,对于存储来说,这是极大的浪费。假如 , 一个普通的文本,大部分字符都只需一个字节就能表示,现在如果需要三个字节才能表示,文本的大小会大出三倍左右。
因此,Unicode 出现了多种存储方式,常见的有 UTF-8、UTF-16、UTF-32,它们分别用不同的二进制格式来表示 Unicode 字符。
UTF-8、UTF-16、UTF-32 中的 “UTF” 是 “Unicode Transformation Format” 的缩写,意思是"Unicode 转换格式",后面的数字表明至少使用多少个比特位来存储字符, 比如:UTF-8 最少需要8个比特位也就是一个字节来存储,对应的, UTF-16 和 UTF-32 分别需要最少 2 个字节 和 4 个字节来存储。
UTF-8编码
UTF-8是一种变长字符编码,被定义为将码点编码为 1 至 4 个字节,具体取决于码点数值中有效二进制位的数量。
UTF-8编码规则:
- 对于单字节的符号,字节的第一位设为 0,后面 7 位为这个符号的 Unicode 码。因此对于英语字母,UTF-8 编码和 ASCII 码是相同的、所以 UTF-8 能兼容 ASCII 编码,这也是互联网普遍采用 UTF-8 的原因之一;
- 对于 n 字节的符号( n > 1),最高位的字节的前 n 位都设为 1,第 n + 1 位设为 0,其他字节的前两位一律设为 10 。剩下的没有提及的二进制位,全部为这个符号的 Unicode 码。
如下表更加直观:
Unicode编码范围(16进制) | UTF-8编码方式(二进制) |
---|---|
000000 - 00007F | 0xxxxxxx ASCII码 |
000080 - 0007FF | 110xxxxx 10xxxxxx |
000800 - 00FFFF | 1110xxxx 10xxxxxx 10xxxxxx |
01 0000 - 10 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
根据上面表格,要解析 UTF-8 编码就很简单了。如果一个字节第一位是 0 ,则这个字节就是一个单独的字符,如果第一位是1 ,则连续有多少个1 ,就表示当前字符占用多少个字节。
如下图,以“中”字举例UTF-8的编码。
- 从字符集表可以查出“中”字的Unicode的编码为0x4E2D。使用二进制表示,共有16位。如上图步骤1;
- 通过上表格可以存储16位二进制数据,需要3个字节,即格式为1110xxxx 10xxxxxx 10xxxxxx。如上图步骤2;
- 将“中”字对应的二进制数据依次填入对应的UTF-8编码的数据位中,如步骤3;
- 最终可以得出“中”字的UTF-8编码是11100100 10111000 10101101,转换为十六进制为0xE4B8AD。
UTF-16编码
UTF-16也是一种变长字符编码,它将字符编码成2字节或者4字节。
UTF-16编码规则:
- 对于 Unicode 码小于 0x10000 的字符, 使用 2 个字节存储,并且是直接存储 Unicode 码,不用进行编码转换;
- 对于 Unicode 码在 0x10000 和 0x10FFFF 之间的字符,使用 4 个字节存储,这 4 个字节分成前后两部分,每个部分各两个字节,其中,前面两个字节的前 6 位二进制固定为 110110,后面两个字节的前 6 位二进制固定为 110111, 前后部分各剩余 10 位二进制表示符号的 Unicode 码 减去 0x10000 的结果;
- 大于 0x10FFFF 的 Unicode 码无法用 UTF-16 编码。
下表是Unicode编码对应UTF-16编码格式:
Unicode编码范围(16进制) | 具体Unicode码(二进制) | UTF-16编码方式(二进制) | 字节 |
---|---|---|---|
0000 0000 - 0000 FFFF | xxxxxxxx xxxxxxxx | xxxxxxxx xxxxxxxx | 2 |
0001 0000 - 0010 FFFF | yy yyyyyyyy xx xxxxxxxx | 110110yy yyyyyyyy 110111xx xxxxxxxx | 4 |
“中” 字的 Unicode 码是0x4E2D,它小于0x10000。根据表格可知,它的 UTF-16 编码占两个字节,并且和 Unicode 码相同,所以 “中” 字的 UTF-16 编码为0x4E2D。
示例:
以这个字母Unicode码为0x10A6F 为例来说明 UTF-16 4 字节的编码,具体步骤如下:
UTF-32编码
UTF-32 是固定长度的编码,始终占用 4 个字节,足以容纳所有的 Unicode 字符,所以直接存储 Unicode 码即可,不需要任何编码转换。虽然浪费了空间,但提高了效率。
UTF字节序(大小端)
最小编码单元是多字节才会有字节序的问题存在,UTF-8 最小编码单元是一字节,所以 它是没有字节序的问题,UTF-16 最小编码单元是 2 个字节,在解析一个 UTF-16 字符之前,需要知道每个编码单元的字节序。
比如:前面提到过,“中” 字的 Unicode 码是 4E2D, “ⵎ” 字符的 Unicode 码是 2D4E, 当我们收到一个 UTF-16 字节流 4E2D 时,计算机如何识别它表示的是字符 “中” 还是 字符 “ⵎ” 呢 ?
所以,对于多字节的编码单元,需要有一个标记显式的告诉计算机,按照什么样的顺序解析字符,也就是字节序,字节序分为 大端字节序 和 小端字节序。
小端字节序简写为 LE( Little-Endian ),表示低位字节在前,高位字节在后, 高位字节保存在内存的高地址端,而低位字节保存在内存的低地址端。
大端字节序简写为 BE( Big-Endian ),表示高位字节在前,低位字节在后,高位字节保存在内存的低地址端,低位字节保存在在内存的高地址端。
注意:在某些工具网页做数据转换测试时失败的话,有可能是大小端问题导致的。
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 |
Qt中转十六进制数据转UTF-8(16)方法
QByteArray hexByteArray = QByteArray::fromHex(text.toLatin1());
QString cnString = QString::fromUtf8(hexByteArray);
其中,text为UTF-8编码格式的十六进制数据,如QString text = "e4bda0";
e4bda0为“你”字的UTF-8的十六进制数据。
如果转UTF-16,则将上面的QString::fromUtf8
函数改为QString::fromUtf16
即可。