阅读本文前,请先熟悉各进制间的转换,否则看起来会有点懵 😂
Unicode
相关:UCS(Universal Character Set)原本标准不同,但现在已经与 Unicode 统一
Unicode 就是一种世界统一的字符编码集合,在这个集合里,世界上每一个字符——任何语言的文字、符号甚至 emoji,都有自己的编号,称为 code point(码点)。这样的编号从 0000 开始,到 10FFFF,可以容纳大于一百万个不重复的字符。
Basic | Supplementary | ||||||||
---|---|---|---|---|---|---|---|---|---|
Plane 0 | Plane 1 | Plane 2 | Plane 3 | Planes 4–13 | Plane 14 | Planes 15–16 | |||
0000–FFFF | 10000–1FFFF | 20000–2FFFF | 30000–3FFFF | 40000–DFFFF | E0000–EFFFF | F0000–10FFFF | |||
Basic Multilingual Plane | Supplementary Multilingual Plane | Supplementary Ideographic Plane | Tertiary Ideographic Plane | unassigned | Supplementary Special-purpose Plane | Supplementary Private Use Area planes | |||
BMP | SMP | SIP | TIP | — | SSP | SPUA-A/B | |||
0000–0FFF |
8000–8FFF |
10000–10FFF |
18000–18FFF |
20000–20FFF |
28000–28FFF |
15: SPUA-A |
点击表中链接可以查看某段位置包含的字符。其中,从 0000
到 ffff
是最常用是平面 0,也称作 Basic Multilingual Plane(BMP),大多数常用汉字都包含在其中。
我们用 Unicode 表示一个字符,约定俗成地 U+
加上这个字的 16 进制码点,例如“汉”就是 U+6C49
。
但是 Unicode 虽然给字符编码了,但是在数据传输时,仅仅是给数据一个号码是不够的,还要想出一种让计算机看懂这个号码的方法,这就引出了 UTF(Unicode Transformation Format),也就是 Unicode 的传输格式。
在 Unicode 维基页的 Mapping and encodings 一节,可以看到多种编码方式,现在常见的 UTF 有 UTF-8、UTF-16、UTF-32,三种格式各有特色。
其中最容易理解的反倒是最不常用的 UTF-32,所以下面会先用 UTF-32 举例——但在此之前,现说一下 code unit(码元)
The minimal bit combination that can represent a unit of encoded text for processing or interchange. The Unicode Standard uses 8-bit code units in the UTF-8 encoding form, 16-bit code units in the UTF-16 encoding form, and 32-bit code units in the UTF-32 encoding form.
上面是 Unicode Consortium 给出的定义,粗略翻译过来就是编码一个字符的最小单元。对于 UTF-8 码元是 8-bit,UTF-16 码元是 16-bit,UTF-32 码元是 32-bit。
UTF-32
UTF-32(UCS-4)是一种空间效率极低、长度不变的编码方式,码元长度为 32-bit,意思就是编码一个字符至少要 32 位,举个例子:
a
的 UTF-32 编码是 00000061
(16 进制)0000 0000 0000 0000 0000 0000 0110 0001
(2 进制,为了方便阅读,添加了空格)
同理可得 ab
就是 0000 0000 0000 0000 0000 0000 0110 0001 0000 0000 0000 0000 0000 0000 0110 0010
那么就很明显可以看出来了,前面的一大堆 0 就是浪费空间的元凶。
看到这里可能会有人提出,把前面的 0 都删掉不就好了吗?
还是上面的例子,ab
删掉 0 之后是 0110 0001 0110 0010
。
问题就来了,谁知道你相邻的 byte 是单个字符还是分别是几个字符呢?0110 0001 0110 0010
不只可以是 ab
也可以是编号为 24930 的那个字符。
所以我们还必须加上一些标志,不能简单地删除前面的 0 了事,当然,加标志就是其他的编码方法了,我们来看看广泛使用的 UTF-8。
UTF-8
UTF-8(8-bit Unicode Transformation Format)是一种特别广泛使用的格式,码元长度为 8-bit,是一种可变长度的编码方式,它的特点是兼容 ASCII。
对于 BMP 的字符,UTF-8 会将其编码为 1 到 3 个码元,非 BMP 编码为 4 个码元。
这就是上面说到的“加标志”,让计算机看懂前后码元到底是一个字符还是多个码元组成一个字符:
- 0 开头,单独一个码元(这部分兼容 ASCII)
- 110 开头,与后面 1 个码元一起组成一个字符
- 1110 开头,与后面 2 个码元一起组成一个字符
- 11110 开头,与后面 3 个码元一起组成一个字符
Number of bytes |
First code point |
Last code point |
Byte 1 | Byte 2 | Byte 3 | Byte 4 |
---|---|---|---|---|---|---|
1 | U+0000 | U+007F | 0xxxxxxx | |||
2 | U+0080 | U+07FF | 110xxxxx | 10xxxxxx | ||
3 | U+0800 | U+FFFF | 1110xxxx | 10xxxxxx | 10xxxxxx | |
4 | U+10000 | U+10FFFF | 11110xxx | 10xxxxxx | 10xxxxxx | 10xxxxxx |
下面是具体的编码例子,基本套路就是:
- 写成二进制
- 在特定的位置拆分开(表中紫、蓝、绿、红数字)
- 添加标志拼接起来(表中黑色数字)
Character | Code point | UTF-8 | ||||
---|---|---|---|---|---|---|
Octal | Binary | Binary | Octal | Hexadecimal | ||
$ | U+0024 | 044 | 010 0100 | 00100100 | 044 | 24 |
¢ | U+00A2 | 0242 | 000 1010 0010 | 11000010 10100010 | 302 242 | C2 A2 |
ह | U+0939 | 004471 | 0000 1001 0011 1001 | 11100000 10100100 10111001 | 340 244 271 | E0 A4 B9 |
€ | U+20AC | 020254 | 0010 0000 1010 1100 | 11100010 10000010 10101100 | 342 202 254 | E2 82 AC |
한 | U+D55C | 152534 | 1101 0101 0101 1100 | 11101101 10010101 10011100 | 355 225 234 | ED 95 9C |
𐍈 | U+10348 | 0201510 | 0 0001 0000 0011 0100 1000 | 11110000 10010000 10001101 10001000 | 360 220 215 210 | F0 90 8D 88 |
UTF-16
UTF-16(UCS-2)即便没有 UTF-8 使用得广泛,仍是一个比较常用的编码方式。
以 UTF-16 16-bit 的码元长度,BMP 字符可以统一用一个码元表示,但是这样 a
就会被编码为 0000 0000 0110 0001
,因此不兼容 ASCII。
但是对于 BMP 后排大户——汉字,UTF-8 会将 U+0800 到 U+FFFF 字符编码为 3 byte,UTF-16 则是稳定的 2 byte,所以如果文件内包含大量中文文本,编码为 UTF-16 会比 UTF-8 的体积会显著缩小。
下表是 UTF-16 的编码方式,可以说与 UTF-8 大同小异。BMP 以外的字符分割后分别添加 110110
和 110111
提示计算机两个码元组成一个字符。
Character | Binary code point | Binary UTF-16 | UTF-16 hex code units |
UTF-16BE hex bytes |
UTF-16LE hex bytes | |
---|---|---|---|---|---|---|
$ | U+0024
|
0000 0000 0010 0100
|
0000 0000 0010 0100
|
0024
|
00 24
|
24 00
|
€ | U+20AC
|
0010 0000 1010 1100
|
0010 0000 1010 1100
|
20AC
|
20 AC
|
AC 20
|
𐐷 | U+10437
|
0001 0000 0100 0011 0111
|
1101 1000 0000 0001 1101 1100 0011 0111
|
D801 DC37
|
D8 01 DC 37
|
01 D8 37 DC
|
𤭢 | U+24B62
|
0010 0100 1011 0110 0010
|
1101 1000 0101 0010 1101 1111 0110 0010
|
D852 DF62
|
D8 52 DF 62
|
52 D8 62 DF
|
html
格式 | 描述 |
---|---|
€ | &#x + 十六进制 + ; |
€ | &# + 十进制 + ; |
€ | & + 名称 + ; |
转义标志是 &
,不管你的 html 文件使用何种编码方式(例如这里写成 <meta http-equiv="Content-Type" content="text/html; charset=shift_jis">
),转义使用的都是Unicode 码点。
使用名称的话可以看这个可以转义的名称列表。
这样的 html 文档内的转义常用于代替空格、<
、>
、&
、"
等 html 里有功能的字符,但是当然不止如此。
iconfont 是前端开发者很熟悉的平台,这个平台可以把图标做成字体,引入这个字体,使得每个图标有一个特定的 Unicode 码位,只要使用转义字符,就能顺利显示该图标。
利用同样的原理,你也可以在 React Native 使用阿里 iconfont 图标。
CSS
CSS 中的转义标志是 \
:
格式 | 描述 |
---|---|
\20AC | 如果下一位是十六进制可用字符必须加空格 |
\0020AC | 必须六位,后面可以不加空格 |
例如 css 选择器本不可以以数字开头,但是使用转义字符就能选择 class 123
.\31 23 { ... }
或 .\00003123 { ... }
字符串
大家熟悉字符串转义应该有这些:处理字符串引号冲突的 \'
\"
换行和拉开距离的 \n
\t
。
下面是使用 Unicode 码点转义成字符的四种方法(使用场景一般也是上面提到的插入 iconfont):
格式 | 描述 |
---|---|
\XXX | 仅限 ISO-8859-1 范围(Unicode U+0000 到 U+00FF),1~3 位奇葩的八进制数 |
\uXXXX | Unicode U+0000 到 U+FFFF(BMP),4 位十六进制 |
\u{X} ... \u{XXXXXX} | Unicode U+0000 到 U+10FFFF,1 到 6 位十六进制 |
\xXX | 仅限 ISO-8859-1 范围(Unicode U+0000 到 U+00FF),2 位十六进制 |
说到 JavaScript 便顺带一提 codePointAt() 和 charCodeAt()的区别:
在 BMP 内,charCodeAt
和 codePointAt
返回的结果相等。
在 BMP 外,字符被分成两块,charCodeAt
的两个结果中明显看到 110110
和 110111
,便能推测出这是 UTF-16。
而 codePointAt
如其名,拿到的直接就是 Unicode 码点,也不用分两位来取了,codePointAt(1)
是没有意义的。
还原方法如下: