接口调试
看到 %E4%B8%AD%E6%96%87 能直接认出是 UTF-8 的两个汉字;看到 JWT 的三段 . 分隔的 Base64 知道怎样还原成 JSON;请求参数里的 + 与 %20 为什么被后端当成不同的东西。
使用指南
字符编码课是一门 14 关的交互式教程,回答开发中反复出现却很少有人说清的几个问题:一个汉字到底占几个字节?为什么 "😀".length 是 2?Base64 末尾的 = 是干什么的?乱码是文件坏了还是能救?每一关给你一段试验文本(可以随意改)和几个视图——每个字符的位、字节、Unicode 码位、UTF-8 逐字节拆解、Base64 的 6 位分组、URL 百分号编码、以及「按别的编码解读会看到什么」——目标是读出某个数、写出某串十六进制、或者输入一个满足条件的字符,判定器即时打勾。
更新于 2026-09-095 个来源约 13 分钟读完
字符编码课是一门 14 关的交互式教程,回答开发中反复出现却很少有人说清的几个问题:一个汉字到底占几个字节?为什么 "😀".length 是 2?Base64 末尾的 = 是干什么的?乱码是文件坏了还是能救?每一关给你一段试验文本(可以随意改)和几个视图——每个字符的位、字节、Unicode 码位、UTF-8 逐字节拆解、Base64 的 6 位分组、URL 百分号编码、以及「按别的编码解读会看到什么」——目标是读出某个数、写出某串十六进制、或者输入一个满足条件的字符,判定器即时打勾。
课程顺序就是编码史的顺序:位与字节 → ASCII 的 128 个约定 → Unicode 给每个字符编号 → UTF-8 怎样把编号变成 1 到 4 个字节 → UTF-16 与 JavaScript 字符串长度 → Base64 把字节搬进纯文本 → URL 编码 → 最后两关专讲乱码:它怎样产生、怎样从 浣犲ソ 反推出 你好。
约 40 分钟学完。面向前后端开发者、数据处理者,以及任何被乱码、Base64、emoji 长度困扰过的人;只需要会数二进制。
%XX;「按其它编码解读」显示同一串字节被当作 GBK、Latin-1、UTF-16 时的样子。?lesson=n 可直达某关。本页上方就是课程本体:改试验文本、读视图、填答案。
第 6 关「UTF-8 两字节」以 café 为样本,要求写出 é 的两个字节:
第 14 关「反推乱码」给出被当作 GBK 解读的乱码 浣犲ソ,要你在试验文本里找出原文:
字符 码位 二进制码位 UTF-8 模板 字节
c U+0063 1100011 0xxxxxxx 63
a U+0061 1100001 0xxxxxxx 61
f U+0066 1100110 0xxxxxxx 66
é U+00E9 000 1110 1001 110xxxxx 10xxxxxx C3 A9
→ 11 位载荷 00011 101001 填进模板 → 11000011 10101001
答案 C3 A9 ✓试验文本 中文 UTF-8 字节 E4 B8 AD E6 96 87 按 GBK 读 → 涓枃
试验文本 你好 UTF-8 字节 E4 BD A0 E5 A5 BD 按 GBK 读 → 浣犲ソ ← 与题目一致
答案 你好 ✓计算机只有 0 和 1。一个位(bit)是一个开关;8 个位组成一个字节(byte),能表示 个不同的值。按位权求和把二进制变成十进制:01000001 = 64 + 1 = 65。所有文字最终都是一串字节,「字符编码」就是一张表,规定哪个数代表哪个字符——不同的表,就是不同的编码。
1963 年的 ASCII 用 7 位定义 128 个字符:0–31 是换行、制表、响铃这类控制字符,48–57 是数字 0–9,65–90 是大写字母,97–122 是小写字母。表不是随意排的:大小写只差 32(二进制第 6 位),所以 c | 0x20 把大写变小写;数字字符减 48 就是数值。十六进制是读字节的惯用写法,两位正好一个字节:0x48 0x69 = Hi。ASCII 只用了 7 位,第 8 位空着——后来各国各自填充这一位(Latin-1、GBK、Shift_JIS……),互不兼容,这正是乱码的历史根源。
Unicode 给全世界的字符统一编号,叫码位(code point),写成 U+ 加十六进制:A 是 U+0041,中 是 U+4E2D,😀 是 U+1F600。空间从 U+0000 到 U+10FFFF,分成 17 个「平面」,常用字符几乎都在第 0 个基本平面(U+0000–U+FFFF),emoji 与生僻汉字在补充平面。关键的一点:码位只是编号,它变成字节的方式由 UTF-8、UTF-16、UTF-32 这些「编码形式」决定。「Unicode」不是一种编码,utf-8 才是。
UTF-8(RFC 3629)用 1–4 个字节表示一个码位,规则全部写在首字节的前缀里:
| 码位范围 | 字节数 | 模板 | 载荷位 |
|---|---|---|---|
| U+0000–U+007F | 1 | 0xxxxxxx |
7 |
| U+0080–U+07FF | 2 | 110xxxxx 10xxxxxx |
11 |
| U+0800–U+FFFF | 3 | 1110xxxx 10xxxxxx 10xxxxxx |
16 |
| U+10000–U+10FFFF | 4 | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx |
21 |
把码位的二进制左侧补零到载荷位数,依次填进 x,就得到字节。é = U+00E9 = 11101001,补到 11 位是 00011 101001,填入两字节模板得到 11000011 10101001 = C3 A9。
这套设计有三个好处:ASCII 文本按 UTF-8 编码后一个字节都不变(完全向下兼容);任何后续字节都以 10 开头,从字节流中间任意位置都能找到下一个字符的起点(自同步,丢一个字节不会让整段错位);不同长度的编码互不重叠,00 这样的零字节永远只代表 U+0000(C 字符串安全)。代价是中文每字 3 字节,比 GBK 的 2 字节多 50%;但英文、标点、代码不受影响,因此 UTF-8 成为超过 98% 网页的编码。
第 7、8 关让你亲手输入一个 3 字节字符与一个 4 字节字符。后者揭示一个至今常见的坑:MySQL 的 utf8 字符集其实只支持到 3 字节,存 emoji 会报错或截断,必须用 utf8mb4。
.lengthJavaScript、Java、C#、Windows API 的字符串内部用 UTF-16:每个码元(code unit)16 位。基本平面的字符占一个码元;补充平面的码位拆成两个码元,叫代理对(surrogate pair),范围 D800–DBFF 与 DC00–DFFF。所以 "😀".length === 2,"😀".split("") 会切成两个无意义的半个,str[0] 拿到的是高位代理。要按真实字符数计数,用 [...str].length(按码位迭代);要按用户感知的「一个字」计数(含组合 emoji、变音符),用 Intl.Segmenter。
Base64(RFC 4648)不是加密,是把任意字节变成 64 个可打印 ASCII 字符(A–Z a–z 0–9 + /)的搬运方式,为的是把二进制塞进只接受文本的地方:邮件附件、JSON 字段、data: URL、JWT。做法是每 3 个字节 = 24 位,切成 4 段 6 位(),每段查表得一个字符:Man = 4D 61 6E = 01001101 01100001 01101110 → 010011 010110 000101 101110 → T W F u。体积因此膨胀到原来的 4/3。
字节数不是 3 的倍数时最后一组不满:缺 1 个字节补一个 =,缺 2 个补两个 =,保证输出长度总是 4 的倍数。A 一个字节 8 位补零到 12 位,切成 010000 01(0000) → QQ,再加 ==。URL-safe 变体把 + / 换成 - _(避免在 URL 中被转义),并常省略 =,解码时按长度补回。
URL 里只有一小部分 ASCII 字符可以原样出现(RFC 3986 §2)。空格、&、=、?、/ 这类有语法含义的保留字符,以及全部非 ASCII 字符,都要写成 % + 字节的十六进制。所以 URL 编码是先 UTF-8,再逐字节加 %:中 → E4 B8 AD → %E4%B8%AD,一个汉字变 9 个字符。JavaScript 里 encodeURIComponent 编码最彻底(连 / ? & = 都编),适合放进参数值;encodeURI 保留结构字符,适合整条地址。表单提交的 application/x-www-form-urlencoded 还会把空格写成 +——这是 HTML 表单的历史约定,不是 RFC 3986 的一部分。
乱码不是数据损坏,而是写的时候用编码 A、读的时候用编码 B。UTF-8 的 中 是 E4 B8 AD:
ä¸ 三个西欧字母——带变音符的字母串是「UTF-8 被当西欧编码读」的标志。E4 B8 是一个汉字 涓,剩下 AD 只有半个——「怪汉字 + 半个字」是「UTF-8 被当 GBK 读」的标志,浣犲ソ、涓枃 都是这一类。�(U+FFFD)。只要原始字节没丢,用正确的编码重新解读,文字就完好如初。因此修复乱码的第一原则是保留原文件、不要反复转换:每转一次都可能把不合法字节替换成 ? 或 �,那才是真正的信息丢失。第 14 关要你做一次逆向:把乱码 浣犲ソ 按 GBK 变回字节 E4 BD A0 E5 A5 BD,再按 UTF-8 读出 你好。现代做法是在 HTTP 头、HTML <meta charset>、数据库连接、文件保存对话框里统一声明 UTF-8,让错配没有机会发生。
UTF-16 有大端 / 小端两种字节顺序,文件开头常放一个字节顺序标记 BOM(FE FF 或 FF FE)。UTF-8 没有字节序问题,但 Windows 记事本曾默认写入 UTF-8 BOM EF BB BF,导致 shell 脚本第一行报错、CSV 首列名前多出不可见字符。课程的 UTF-8 拆解视图里若看到这三个字节开头,就知道来源。
TextDecoder,不讲 GB18030、Big5、Shift_JIS 的内部结构。length 就是字符数」:在 JavaScript 里是 UTF-16 码元数,emoji 与部分生僻字算 2;数据库 VARCHAR(n) 的 n 在不同数据库里可能是字节数也可能是字符数。encodeURIComponent,否则 &、#、空格会破坏结构。看到 %E4%B8%AD%E6%96%87 能直接认出是 UTF-8 的两个汉字;看到 JWT 的三段 . 分隔的 Base64 知道怎样还原成 JSON;请求参数里的 + 与 %20 为什么被后端当成不同的东西。
用第 13–14 关的方法判断是「UTF-8 被当 GBK」还是反过来,再用编辑器的「以编码重新打开」而不是「转换编码」——保留原字节是第一原则。
存 emoji 用 utf8mb4;估算 UTF-8 存储量时中文按 3 字节算;VARCHAR 长度单位要查清是字节还是字符。
截断用户昵称时用 [...str].slice(0, n) 而不是 str.slice(0, n),避免把 emoji 切成半个;统计字数时决定用码元、码位还是字素簇。
带肤色修饰或用零宽连接符(ZWJ)组合的 emoji(如 👍🏽、👨👩👧)由多个码位组成,码位视图会逐个列出。第 8 关要求恰好一个 4 字节字符,请用不带修饰的基础 emoji,如 😀。
都不要求。c3a9、C3 A9、c3 a9 都算对。
课程用浏览器内置的 TextDecoder('gbk')(实际是 GB18030 的超集),遇到不合法序列输出 �;有的工具会输出 ? 或直接跳过,字形不同但含义一样——都是「这里无法解读」。
如果原字节完整、只是解读方式错了,能:用编辑器以正确编码重新打开即可。如果文件已经被「转换」过并出现大量 ?,那部分信息已经丢失,课程也救不回来。
当前浏览器的 localStorage,不上传;「分享本关」的链接只带关卡号,不带进度。
课程全部在浏览器本地运行:试验文本的编码、解码、Base64 与 URL 编码都由页面内的 TextEncoder / TextDecoder 与纯函数完成,不发送到任何服务器;通关进度存在本机 localStorage。
TextDecoder 支持的编码及其解码算法,课程乱码演示所用 GBK / windows-1252 解码器的规范:https://encoding.spec.whatwg.org/(访问日期:2026-09-09)更新于 2026-09-09
14 关弄懂位、字节、ASCII、Unicode 码位、UTF-8 变长编码、Base64 分组、URL 编码与乱码成因
目标试验文本是字母 A,它的一个字节显示为 01000001。把这个二进制数换算成十进制填入答案框。
| 字符 | 码位 | UTF-8 字节 | UTF-16 码元 | 字节数 |
|---|---|---|---|---|
| A | U+0041 | 41 | 0041 | 1 |
计算机只存 0 和 1。一个「位」(bit)是一个开关,8 个位组成一个「字节」(byte),能表示 0–255 共 256 个值。二进制按位权求和:01000001 = 64 + 1 = 65。所有文字、图片、声音最终都是一串字节,「编码」就是规定「哪个数代表哪个字符」。