雜記 › 技術筆記 › \u4e2d 是什麼?中文與 Unicode 編碼線上互轉

\u4e2d 是什麼?中文與 Unicode 編碼線上互轉

接手那個跑了七年的老系統時,我要改的只是一句提示文案:在「目前庫存不足」後面加個括號說明。檔案是 message_zh.properties,1846 行、326 個 key,整個畫面都是 \uXXXX,搜「庫存」兩個字一個都命中不了。我先花二十分鐘逐行比對定位到那一行,改的時候又手滑把 \u5de5 少打了一位,編譯直接報 illegal escape character。繞這一圈才弄清:\u 開頭那些不是亂碼,是同一批漢字換了一種寫法。

一、碼點是編號,\uXXXX 只是它的十六進位寫法

Unicode 給每個字元分配一個編號,叫碼點,與位元組、與用什麼語言都無關。\u4e2d 的意思就是「這個字元的碼點,十六進位寫作 4E2D」。同一個「中」字,可以有好幾種長相。

漢字碼點Unicode 轉義HTML 數字實體碼點十進位
中U+4E2D\u4e2d中20013
文U+6587\u6587有25991
工U+5DE5\u5de5巧24039
具U+5177\u5177具20855

範圍上也值得記兩個數:基本多文種平面是 U+0000 到 U+FFFF,一共 65536 個碼位,常用符號和漢字大多落在裡面;漢字主區段 U+4E00 到 U+9FFF,佔 20992 個碼位。超出 U+FFFF 的生僻字和 emoji 沒辦法用單一四位轉義表示,得用代理對:U+1F600 要寫成 \ud83d\ude00 兩段。

二、五種「看著都像亂碼」的東西,不在同一層

把這幾層混起來處理,是損壞資料最快的方式。

長相實際是什麼屬於哪一層
\u4e2d\u6587字元碼點的轉義寫法原始碼 / JSON 字串層
中同一個碼點,十進位實體HTML 標記層
%E4%B8%AD「中」的 UTF-8 三個位元組 E4 B8 AD 被百分號化URL 傳輸層
D6 D0「中」在 GBK 裡的兩個位元組位元組編碼層
中文UTF-8 位元組被當成 Latin-1 顯示已經錯了,是顯示事故

最後那行是真亂碼:位元組解釋錯了,沒有任何「還原成中文」的按鈕能把它救回來,只能回到源頭改解碼方式。同理,「錕斤拷」是把替換字元 EF BF BD 再用 GBK 讀了一遍的結果。我見過有人拿 decodeURIComponent 去解 %u4E2D,只換來一句 URI malformed——那是早年 escape() 留下的四不像寫法,兩頭都不算。

三、我最後是怎麼改那一行的

  1. 定位。把整個檔案的內容用中文 Unicode 互轉還原一版純中文,在這份還原結果裡按「庫存」搜尋,拿到 key 名,再回到原檔案改那一行。原檔案一個字元不動,避免整篇往返轉換帶來看不見的差異。
  2. 確認形式。properties 這種檔案按行讀,轉義符寫單反斜線 \u5de5;如果這段文字要嵌進 Java 原始碼字串,就得寫成 \\u5de5。少一位或多一位反斜線,編譯器只會甩一句非法轉義。
  3. 產生要寫回去的那一行時,我用「僅轉非 ASCII」這一檔,英文字母、數字和半角標點原樣保留,diff 裡只會顯示我真正改動的那幾個字;全選轉換會把整行都變成轉義,審查時根本看不出改了哪裡。
  4. 需要輸出成頁面能直接用的形式時,同一份文字換成十進位模式,拿到 中 這種實體寫法。

百分號那一層我用的是別的工具:URL 編解碼處理的是位元組序列,%E4%B8%AD 歸它管;文字編碼轉換管全形半形和 ASCII 值。三者不能互相代替,層級不一樣。

四、幾個可以直接抄走的數字

  • 同樣一段「中文」:2 個字元,UTF-8 下 6 個位元組,GBK 下 4 個位元組,寫成 \u4e2d\u6587 是 12 個 ASCII 字元——一個漢字的轉義固定佔 6 個字元。
  • 按字元數算額度的地方最吃虧:簽名、簡訊範本、設定項全轉義等於漲 6 倍,一旦從中間截斷,留下 \u4e 這種半截就是語法錯誤。
  • 看不見的那幾個字元:\u3000 是全形空格(12288),\u00a0 是不換行空格(160),\u201c / \u201d 是中文彎引號(8220 / 8221)。從文件或聊天視窗複製過的文案十有八九帶著它們,先還原成中文再尋找取代才有意義。
  • 反斜線的層數別記錯:properties 裡是單反斜線 \u5de5,進 Java 原始碼字串要寫 \\u5de5,這段文字再進 JSON 時又得多剝一層。每跨一層就多一次轉義,出錯多半是在中間某一層少寫了。

五、常見疑問

Q:\u4e2d 裡的 4e2d 是什麼?

是「中」這個字元的 Unicode 碼點,十六進位 4E2D,換成十進位就是 20013,所以 HTML 實體裡寫作 中。兩者指向同一個字元,只是十六進位和十進位的差別。

Q:原始碼裡寫轉義和直接寫中文,執行起來有差別嗎?

解析之後完全等價,是同一個字元。差別在人這一側:轉義寫法讓全文搜尋、程式碼審查和對文案都失效,而且要求檔案本身只能安全地容納 ASCII。

Q:為什麼以前 Java 的設定檔非要轉義?

PropertyResourceBundle 在 JDK 9 之前固定按 ISO-8859-1 讀這類檔案,中文必須以 \uXXXX 形式存進去,早年 JDK 還專門配了 native2ascii 這個轉換工具(後來廢棄)。這解釋了我手上那份檔案為什麼滿是轉義——它是歷史限制留下的寫法,不是壞資料。

Q:文案裡的 emoji 為什麼字數總是對不上?

因為超出 BMP 的字元要佔兩個 UTF-16 碼元:'\ud83d\ude00'.length 是 2,一個笑臉在按長度校驗的程式碼裡被算成兩個字。要按碼點統計就用 codePointAt 一類寫法,原始碼裡也可以寫 \u{1f600}(ES6 起支援)。至於 Invalid Unicode escape sequence 那個報錯,通常來自被截斷的轉義,比如只剩 \u4e,湊不滿四位十六進位。

Q:幾百行一起轉會不會改壞?

轉換本身是無損的,風險在覆蓋原檔。我的做法是轉出來的那份只用來查、不用來存,實際改動落在原檔案的那幾行,提交前用 diff 確認行數還是 1846 行、只有目標 key 變了。

那句「目前庫存不足(可在設定裡調整)」最後加了進去,用的是原來的單反斜線轉義寫法,編譯過了。回頭看,那二十分鐘裡真正值錢的經驗不是「找到轉換工具」,而是先分清自己面對的是哪一層:轉義和實體是同一個碼點的兩種寫法,還原無損;百分號那層歸 URL 管;至於 中文 這種位元組已經解錯的,只能回到讀寫兩端改解碼方式,任何「轉成中文」都救不回來。

📎 相關文章

PNG 壓了三遍只減 426 位元組:無損壓縮的真實邊界 2026-10-22 WebP 轉 PNG 後體積漲了幾倍:什麼時候必須轉,怎麼把漲幅壓住 2026-10-22 截圖縮到四分之一,小字糊成一團的三種原因 2026-10-22 同樣裁 300×300,頭像為什麼總偏右下角半格 2026-10-22 按每月 1.7 倍預估 18 個月,腳本和表格為什麼給出兩個數 2026-10-22