接口調試
看到 %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。所有文字、图片、声音最终都是一串字节,「编码」就是规定「哪个数代表哪个字符」。