ASCII vs Unicode vs UTF-8:字符编码终于搞明白了
爬虫抓回来的数据全是 ???,MySQL 存中文显示乱码,PHP strlen("你好") 返回 6 而不是 2。这类问题 99% 的根源不在数据库,不在浏览器,而在于没搞清 ASCII、Unicode、UTF-8 三者到底是什么关系。
这篇把三者拆开讲透,让你下次遇到字符问题能立刻判断是字符集问题还是编码问题。想直接查看或转换字符,用我们的 Unicode 编解码工具,可以显示任意文本的码位和 UTF-8 字节。
一分钟对照表
| | ASCII | Unicode | UTF-8 |
|---|-------|---------|-------|
| 是什么 | 7 位字符集 | 统一字符集(码位) | Unicode 的字节编码方式 |
| 规模 | 128 个字符 | 已分配 14 万+ 码位 | 每码位 1–4 字节 |
| 中文 / emoji | 不支持 | 支持(码位) | 支持(多字节序列) |
| 与 ASCII 兼容? | — | 前 128 即 ASCII | 对 ASCII 字节完全相同 |
| 常见坑 | 假定「一字符=一字节」 | 把「字符集」当成「编码」 | 用字节长度当字符数(strlen) |
口诀: Unicode 回答「是哪个字?」;UTF-8 回答「存成哪些字节?」;ASCII 是两者的前 128 个。
ASCII:128 个字符的 7-bit 时代
ASCII(American Standard Code for Information Interchange)诞生于 1960 年代,用 7 个 bit 表示一个字符,共 128 个码位:
| 范围 | 内容 | 数量 | |------|------|------| | 0-31 | 控制字符(换行、制表等) | 32 | | 32-126 | 可打印字符(字母、数字、符号) | 95 | | 127 | DEL | 1 |
'A' = 65 = 0x41
'a' = 97 = 0x61
'0' = 48 = 0x30
' ' = 32 = 0x20
ASCII 解决了英文的存储问题,但仅限英文。德文的 ä、中文的"中"、日文的"あ"、Emoji,全部无能为力。
Unicode:覆盖全世界的字符集
Unicode 是一张大表,给全世界几乎所有字符都分配了一个唯一编号(码位 code point)。范围从 U+0000 到 U+10FFFF,理论上能容纳 110 多万个字符,目前已分配超过 14 万。
U+0041 A (Latin Capital Letter A)
U+4E2D 中 (CJK Unified Ideograph)
U+1F600 😀 (Grinning Face)
U+1F4A9 💩 (Pile of Poo)
注意 Unicode 只规定"哪个字符对应哪个编号",没有规定这个编号在内存里怎么存。把它存成字节序列的方式叫"编码",UTF-8 就是其中一种,也是最流行的一种。
UTF-8:Unicode 的可变长度编码
UTF-8 是 Unicode 的一种实现方式,用 1 到 4 个字节存一个码位:
| 字节长度 | 码位范围 | 典型字符 | 例子 |
|----------|----------|----------|------|
| 1 字节 | U+0000 - U+007F | ASCII | A → 0x41 |
| 2 字节 | U+0080 - U+07FF | 拉丁扩展、希腊、西里尔 | é → 0xC3 0xA9 |
| 3 字节 | U+0800 - U+FFFF | CJK 中文日韩 | 中 → 0xE4 0xB8 0xAD |
| 4 字节 | U+10000+ | Emoji、古文字 | 😀 → 0xF0 0x9F 0x98 0x80 |
变长编码的好处:英文文本和 ASCII 一样紧凑,CJK 文本虽然多一个字节但能正确存储,没有浪费。
UTF-8 向后兼容 ASCII
这是 UTF-8 设计上最聪明的地方。前 128 个码位(U+0000 到 U+007F)在 UTF-8 里用 1 个字节表示,而且字节值和 ASCII 完全一样:
ASCII 'A' = 0x41
UTF-8 'A' = 0x41 # 同一个字节
任何纯 ASCII 文本,本身就是合法的 UTF-8 文本。这就是为什么 HTML、Email、HTTP 协议至今默认 UTF-8,旧的英文系统不用改一行代码就能继续跑。
字符 vs 字节:以 "你好" 为例
"你好" 是 2 个字符,但 UTF-8 编码后是 6 个字节:
字符: 你 好
码位: U+4F60 U+597D
UTF-8: E4 BD A0 E5 A5 BD
字节: ----- 3 ----- ----- 3 -----
$ echo -n "你好" | xxd
00000000: e4bd a0e5 a5bd ......
$ echo -n "你好" | wc -c
6
PHP 和 C 里的 strlen 数的是字节,所以 strlen("你好") 返回 6。要用 mb_strlen 才能得到字符数 2:
<?php
$s = "你好";
echo strlen($s); // 6 (字节数)
echo mb_strlen($s); // 2 (字符数)
这个区别是 90% 字符相关 bug 的来源。前端 String.length 在 JavaScript 里数的是 UTF-16 码元,对 😀 这种 4 字节字符会返回 2,又是另一层坑。
hex dump 对比
"A" hex: 41 # 1 字节
"中" hex: E4 B8 AD # 3 字节
"😀" hex: F0 9F 98 80 # 4 字节
"A中" hex: 41 E4 B8 AD # 混合编码,共 4 字节
UTF-8 的解码器靠首字节的高位判断后续字节数:
- 首字节
0xxxxxxx:1 字节字符 - 首字节
110xxxxx:2 字节字符,后跟 1 个10xxxxxx - 首字节
1110xxxx:3 字节字符,后跟 2 个10xxxxxx - 首字节
11110xxx:4 字节字符,后跟 3 个10xxxxxx
这就是为什么 UTF-8 能自我同步:从字节流中间任意位置开始,最多回溯几个字节就能定位字符边界。
常见编码 bug
1. 多字节字符被截断
// 错误:按字节长度截中文字符串可能切断一个 3 字节序列
const s = "你好世界";
s.substr(0, 3); // 可能拿到半个"你"
现代 JavaScript slice 按字符数操作,安全。但 Buffer 操作、fetch 流处理、字节级截断还是容易踩坑。
2. MySQL utf8 vs utf8mb4
MySQL 的 utf8 字符集最多存 3 字节字符,存 Emoji 会失败。要用 utf8mb4。MySQL 8.0 已经把默认改成了 utf8mb4,但老库还很多。
3. BOM 导致首字符识别错
BOM(U+FEFF)放在文件开头,告诉读取器编码是 UTF-8。但 BOM 会作为内容的一部分出现在 PHP header() 之前的输出里,导致 "headers already sent"。多数情况下不要 BOM,存为 UTF-8 without BOM。
何时该考虑编码
- 存数据库前:确认表字符集是 utf8mb4,连接编码也是 utf8mb4
- 写 HTTP 响应:
Content-Type: application/json; charset=utf-8 - 解析二进制协议:明确每个字段的字节序和编码
- 跨语言传字符串:永远 UTF-8,不要用本地编码
在本地完成编码调试
调编码问题需要 hex dump、码位查询、字节计数。这些操作最好别把数据贴到陌生网站,尤其是带敏感内容的字符串。
Unicode 编解码工具 能列出每个字符的码位和字节序列,Base64 编解码工具 帮你确认 Base64 里嵌的中文是否完整还原,字符频率统计 能在分析大段文本时验证字符是否正确。全部在浏览器本地运行,数据不上传。
FAQ
UTF-8 等于 Unicode 吗?
不等于。Unicode 是字符表(码位),UTF-8 只是把码位编成字节的一种方式。同一套 Unicode 还可以用 UTF-16 / UTF-32 编码。
为什么 strlen("你好") 是 6?
PHP/C 的 strlen 数的是字节。一个汉字在 UTF-8 里通常占 3 字节,两个字就是 6。要字符数请用 mb_strlen(或语言自带的字符计数 API)。
数据库该选哪一种?
文本按 Unicode 存,底层用 UTF-8 字节。MySQL 请用 utf8mb4(不要用旧的 utf8),连接字符集也设成 utf8mb4。
下次遇到乱码,先看 hex,再看码位,最后才是字符集。问题往往比想象中简单。
本文由 ToolVault 工具匣提供。相关工具:Unicode 编解码、字符频率、Base64 解码。相关阅读:Base64 中文编码指南、URL 编码空格与特殊字符。更多工具见首页。
相关工具
相关文章
图片格式详解:JPG、PNG、WebP、HEIC 与 SVG——怎么选、何时转、如何转不损画质
图片格式选择的实用指南:五种格式各自擅长什么、压缩何时伤画质、透明通道为什么改变一切、以及格式间转换的保质量规则。全部转换可在浏览器本地完成。
在线 Base64 转图片工具:将编码字符串还原为图片文件
学习如何将 Base64 编码字符串还原为图片文件。了解 Base64 解码原理、常见的 Base64 图片格式,以及在 Web 开发中的实际应用场景。
为什么 Base64 会让文件变大 33%?编码开销原理详解
Base64 size increase(体积膨胀)约 33%:每 3 字节变成 4 字符。用 Hi、Hello、1MB 三个例子算清这笔账,并说明何时真的要算。