cv.cm

知识库 · 文本、数据与安全

Unicode 和 UTF-8 有什么区别?

阅读约 6 分钟

简短回答

Unicode 是字符集:它给每个字符分配一个编号,叫作码点,例如「é」是 U+00E9。UTF-8 是编码方式:把这些编号存成字节的规则。ASCII 字符占 1 个字节,其他字符占 2 到 4 个字节,所以「é」会变成 C3 A9。Unicode 规定是哪个字符,UTF-8 规定它如何写入磁盘或通过网络传输。

打开十六进制

先有码点,再有字节

Unicode 定义的码位空间从 U+0000 到 U+10FFFF,共可容纳 1,114,112 个码点,分为 17 个平面,每个平面 65,536 个。其中已有超过 150,000 个分配给了字符,涵盖现代和历史文字、符号以及 emoji。码点写作 U+ 加至少四位十六进制数字。

码点只是一个数字。要保存或传输文本,就必须把它转换成字节,而转换方法有好几种:UTF-8、UTF-16 和 UTF-32 都是同一套 Unicode 字符的不同编码方式。

字符码点UTF-8 字节UTF-16(大端序)
AU+00414100 41
éU+00E9C3 A900 E9
€U+20ACE2 82 AC20 AC
日U+65E5E6 97 A565 E5
😀U+1F600F0 9F 98 80D8 3D DE 00

UTF-8 如何决定用 1、2、3 还是 4 个字节

UTF-8 由 RFC 3629 规定,是一种变长编码。首字节的前几位表示后面还跟几个字节,每个后续字节都以 10 开头。这让字节流具有自同步性:解码器即使从某个字符中间开始读,也能跳到下一个起始字节。

码点范围字节数位模式
U+0000 – U+007F10xxxxxxx
U+0080 – U+07FF2110xxxxx 10xxxxxx
U+0800 – U+FFFF31110xxxx 10xxxxxx 10xxxxxx
U+10000 – U+10FFFF411110xxx 10xxxxxx 10xxxxxx 10xxxxxx

计算示例:「é」是 U+00E9,二进制为 000 1110 1001,需要用 2 字节模式。高 5 位 00011 填入 110xxxxx,得到 11000011(C3);低 6 位 101001 填入 10xxxxxx,得到 10101001(A9)。

前 128 个码点编码后与 ASCII 的单字节完全相同,所以任何合法的 ASCII 文件本身就是合法的 UTF-8。这种向后兼容性是 UTF-8 成为网页默认编码的主要原因,目前约 98% 的网站使用 UTF-8。

UTF-8、UTF-16 和 UTF-32 对比

编码码元大小每个字符的字节数常见于
UTF-88 位1–4网页、JSON、Linux 和 macOS 文件、大多数 API
UTF-1616 位2 或 4JavaScript 字符串、Java、Windows API
UTF-3232 位固定 4需要定长的内部处理

UTF-16 把 U+FFFF 以上的字符存成由两个 16 位码元组成的代理对。所以在 JavaScript 中 "😀".length 是 2:这门语言统计的是 UTF-16 码元,而不是字符。用 [..."😀"].length 遍历则返回 1。

对英文文本来说,UTF-8 最紧凑。对以中文、日文或韩文为主的文本,UTF-16 可能更小,因为这些字符在 UTF-8 中占 3 个字节,在 UTF-16 中只占 2 个。

字节顺序标记(BOM)

BOM 是放在文件最开头的字符 U+FEFF。在 UTF-16 和 UTF-32 中,它告诉读取方字节是大端序还是小端序。在 UTF-8 中它编码为 EF BB BF,只起签名作用,因为 UTF-8 没有字节序问题。

Unicode FAQ 指出,UTF-8 BOM 既不必要也不推荐。但有些 Windows 编辑器还是会加上它,结果会破坏 shell 脚本(#! 行不再位于开头)、CSV 表头,以及不预期 BOM 的 JSON 解析器。如果文件开头显示 ,那就是被当成 Windows-1252 读取的 UTF-8 BOM。

文字为什么会变成 é 或 ���

乱码(mojibake)出现的原因,是用来解码字节的编码与写入时的编码不一致:

  • é 显示成 é:UTF-8 字节 C3 A9 被当作 Windows-1252 或 Latin-1 读取,C3 对应 Ã,A9 对应 ©。
  • �(U+FFFD,替换字符):解码器遇到了不合法的 UTF-8 字节,常见于把 Latin-1 文本当作 UTF-8 读取。
  • 数据库里出现问号:列或连接使用的字符集无法容纳该字符,写入时就被替换掉了,原字符已经丢失。

解决办法是在每个环节都声明编码:HTML 中的 <meta charset="utf-8">,HTTP 头中的 Content-Type: text/html; charset=utf-8,数据库列和连接也都用 UTF-8。MySQL 请使用 utf8mb4;旧的 utf8 字符集每个字符最多存 3 个字节,无法保存 emoji。

常见问题

UTF-8 就是 Unicode 吗?

不是。Unicode 是字符及其编号的清单,UTF-8 是把这些编号存成字节的一种方式。人们说「Unicode」时,常常指的是 UTF-8 文本。

UTF-8 中一个字符占几个字节?

1 到 4 个。ASCII 字母占 1 个字节,大多数欧洲带重音字母占 2 个,大多数中文、日文和韩文字符占 3 个,emoji 占 4 个。

UTF-8 能表示所有字符吗?

能。UTF-8 可以编码从 U+0000 到 U+10FFFF 的所有 Unicode 码点,所以 Unicode 定义的任何字符都能用 UTF-8 写出来。

保存文件时要不要带 BOM?

对 UTF-8 来说,通常不需要。BOM 是可选的,对字节序毫无作用,还会干扰那些认为开头字节就是正文的工具。不过某些旧版 Excel 需要 BOM 才能正确打开 UTF-8 编码的 CSV 文件。

全部知识文章