字符编码:Unicode 与 UTF-8 详解
更新 2026-07-06
你见过:本该读作 José 的名字变成了 José,或者一个表情符号变成了一堆问号方框。这种乱码有个名字 —— mojibake(文字化け) —— 而它几乎总是字符编码不匹配。本指南讲清字符、Unicode 和 UTF-8 究竟是什么,让这类 bug 不再神秘。
计算机存的是数字,不是字母
在最底层,计算机只存数字(字节)。要存文本,我们需要一张约定好的表,把每个字符映射到一个数字,再映射回来。这张表就是 字符编码。如果一个程序用一张表写文本,另一个程序用另一张表读,数字就会被解读成错误的字符 —— 你就得到乱码。
ASCII:小小的起点
最早广泛使用的编码 ASCII 为 128 个字符定义了数字:英文字母、数字、标点和一些控制码。这对英语够用,但没有位置留给 é、ü、ß、你、😀,或世界各语言使用的成千上万个字符。几十年里人人都发明了自己彼此不兼容的扩展,这正是文本在系统之间穿行时会坏掉的原因。
Unicode:为每个字符分配一个数字
Unicode 是通用的解决方案:一个单一标准,为每种书写系统里的每个字符,外加符号和表情符号,分配一个唯一的数字 —— 称为 码点(code point)。码点写作 U+ 后跟一个十六进制数:
A → U+0041 é → U+00E9 你 → U+4F60 😀 → U+1F600
关键在于,Unicode 定义了 数字,但没定义如何把它们存成字节。那是一个单独的决定 —— 而这正是 UTF-8 登场的地方。
UTF-8:Unicode 如何变成字节
UTF-8 是把 Unicode 码点编码成字节的一种方式,也是网络压倒性使用的那种。它精巧的设计在于它是 变长的:
- 128 个 ASCII 字符各占 1 字节 —— 所以 UTF-8 与 ASCII 向后兼容。
- 大多数带重音的拉丁字符以及希腊/西里尔字符占 2 字节。
- 包括中文、日文和韩文在内的大多数其他文字占 3 字节。
- 表情符号和更罕见的字符占 4 字节。
这就是为什么一个“100 字符”的字符串并不总是 100 字节:一个字符可能是好几个字节。它也解释了那个经典的 José bug —— 那是 é 的 UTF-8 字节,被当成较旧的西方编码、一字节一字节地误读了。
字符、码点和字节
三个相关却不同的概念不断让人栽跟头:
- 字符(Character) —— 人所看到的(字母
é)。 - 码点(Code point) —— 它的 Unicode 数字(
U+00E9)。 - 字节(Byte) —— 它被存储的方式(在 UTF-8 中,是两个字节
0xC3 0xA9)。
把码点原样写出来看,往往是诊断文本问题最快的办法 —— 例如,一个原来是不换行空格(U+00A0)的“空格”。我们的 Unicode 转换器 会显示任意文本背后的码点,并在字符与转义序列之间转换。
如何避免乱码
- 到处都用 UTF-8。 把文件保存为 UTF-8 并声明它 —— 在网页上用
<meta charset="utf-8">,在 HTTP 中用Content-Type的 charset,在数据库中用 UTF-8 排序规则。 - 从头到尾保持一致。 大多数损坏发生在一方假设了不同编码的边界处。
- 为正确的上下文转义。 要把特殊字符安全地放进 HTML,用 HTML 实体;要放进 URL,用 URL 编码。
简短版:Unicode 给每个字符一个数字,UTF-8 把这些数字变成字节,而如果人人都同意使用 UTF-8,文本就不再坏掉。
更多 数据格式与编码
查看全部 数据格式与编码 指南 →本指南提到的工具