码位、U+、平面与代理对
最后更新 2026-11-02
本页目录
Unicode 的第一件事是把「字符」变成一个编号,这个编号叫码位(code point)。它和字符在文件里怎么存(字节)是两回事——搞清楚这一层,后面那些「长度为什么是 2」「为什么一个字要 7 个码位」才有解释。
码位就是编号
每个字符有一个 0 到 0x10FFFF 之间的编号。写作 U+ 加至少四位十六进制:
| 字符 | 码位 |
|---|---|
A |
U+0041 |
中 |
U+4E2D |
😀 |
U+1F600 |
U+ 后面的位数不是固定的:BMP 内的码位习惯写四位,超出时写五位或六位。
平面
把 0–0x10FFFF 每 65,536 个码位切成一块,叫一个平面(plane):
| 平面 | 范围 | 内容 |
|---|---|---|
| 0 | U+0000–U+FFFF |
基本多文种平面(BMP),绝大多数日常字符 |
| 1 | U+10000–U+1FFFF |
补充多文种平面:emoji、古文字 |
| 2 | U+20000–U+2FFFF |
补充表意平面:CJK 扩展 B 等 |
| 3–13 | —— | 未分配 |
| 14 | U+E0000–U+EFFFF |
补充特殊用途 |
| 15–16 | U+F0000–U+10FFFF |
私用区 |
「私用区」是留给各家自己定义的:字体可以在这里放任何图形,Unicode 不管它叫什么。
代理区不是字符
U+D800–U+DFFF 这一千多个码位永远不对应字符,它们是 UTF-16 的机制:一个 BMP 之外的字符用两个 16 位码元拼出来,这对码元就落在这个区间里。
所以「代理区里有没有字符」这个问题没有意义——它们只是编码的中转站。
UTF-8 与 UTF-16
- UTF-8 用 1–4 个字节表示一个码位,ASCII 兼容;网页与文件默认用它;
- UTF-16 用 2 个字节(一个码元)表示 BMP,BMP 之外用两个码元(代理对)。
JavaScript 的字符串是 UTF-16 码元序列,于是:
'😀'.length // 2 ← 两个码元
[...'😀'].length // 1 ← 一个码位
'😀'.codePointAt(0) // 128512 (0x1F600)
String.fromCodePoint(0x1F600) // '😀'
同样,slice(0, 1) 会把一个 emoji 砍成半个代理对,得到乱码。要按「字符」遍历,用 for...of 或展开成数组,而不是下标。
一个「字」可能是好几个码位
码位是编码单位,不是「看起来的一个字」:
é可以是U+00E9一个码位,也可以是U+0065+U+0301(字母 + 重音);- 家庭 emoji 👨👩👧 是「男人 + 零宽连接符 + 女人 + 零宽连接符 + 女孩」,五个码位;
- 国旗 emoji 是两个「区域指示符」拼出来的。
负责把这些码位组合成「一个视觉字符」的过程叫字素簇(grapheme cluster)。它由 Unicode 的分段规则决定,不是字节数。
变体选择符
有些字符有多个字形(比如「︎」与「️」这类 emoji 的文本/图形两种样子),用变体选择符指定:
U+FE0E:要文本样式;U+FE0F:要 emoji 样式。
它们本身不显示,只是「附在前一个字符上」的修饰。字体要在 cmap 的变体序列表里声明支持,否则选择符会被忽略。
本站怎么展示
字符抽屉里显示的是 U+4E2D 这种标准写法,加一行十进制,方便你去别处查。区块用的是 Unicode 官方 Blocks.txt 的名字与范围。
延伸阅读
- Unicode 标准第 2 章(General Structure)
- UAX #29(字素簇边界)
- MDN:
String#codePointAt、String.fromCodePoint