在 Web 開發中,URL 傳遞中文參數時需進行編碼(如 %E4%BD%A0),接收端再解碼還原。雖然 JDK 提供 URLEncoder/URLDecoder,但某些場景(舊系統兼容、特定編碼規則)仍需手動實現。本文提供基於 UTF-8 的完整編解碼工具類。
package com.gjob.common;
public class URLtoUTF8 {
/** 將字符串轉為 %XX 格式(UTF-8 編碼) */
public static String toUtf8String(String s) {
StringBuffer sb = new StringBuffer();
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
// ASCII 字符直接保留
if (c >= 0 && c <= 255) {
sb.append(c);
} else {
try {
byte[] b = String.valueOf(c).getBytes("UTF-8");
for (byte byteVal : b) {
int k = byteVal < 0 ? byteVal + 256 : byteVal;
sb.append("%").append(Integer.toHexString(k).toUpperCase());
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
return sb.toString();
}
/** 將 %XX 格式解碼為原始字符串(支持 UTF-8 多字節) */
public static String unescape(String s) {
StringBuffer sbuf = new StringBuffer();
int l = s.length();
int ch = -1;
int b, sumb = 0;
int more = -1;
for (int i = 0; i < l; i++) {
// 1. 獲取下一個字節 b
switch (ch = s.charAt(i)) {
case '%':
int hb = Character.digit(s.charAt(++i), 16);
int lb = Character.digit(s.charAt(++i), 16);
b = (hb << 4) | lb;
break;
case '+':
b = ' ';
break;
default:
b = ch;
}
// 2. UTF-8 解碼邏輯(狀態機)
if ((b & 0xC0) == 0x80) { // 10xxxxxx 續位
sumb = (sumb << 6) | (b & 0x3F);
if (--more == 0) sbuf.append((char) sumb);
} else if ((b & 0x80) == 0x00) { // 0xxxxxxx 單字節
sbuf.append((char) b);
} else if ((b & 0xE0) == 0xC0) { // 110xxxxx 雙字節首位
sumb = b & 0x1F; more = 1;
} else if ((b & 0xF0) == 0xE0) { // 1110xxxx 三字節首位
sumb = b & 0x0F; more = 2;
} else if ((b & 0xF8) == 0xF0) { // 11110xxx 四字節首位
sumb = b & 0x07; more = 3;
} else if ((b & 0xFC) == 0xF8) { // 111110xx 五字節首位
sumb = b & 0x03; more = 4;
} else { // 1111110x 六字節首位
sumb = b & 0x01; more = 5;
}
}
return sbuf.toString();
}
public static void main(String[] args) {
System.out.println(toUtf8String("你")); // %E4%BD%A0
System.out.println(unescape("%E4%BD%A0%20%E5%A5%BD")); // 你 好
}
}
getBytes("UTF-8") 獲取字節數組,每字節轉為無符號整數後格式化為 %XX 大寫十六進制。逐字符掃描,遇到 % 解析兩位十六進制得到字節 b,再根據 UTF-8 編碼規則判斷字節類型:
| 字節模式 | 含義 | 處理 |
|---|---|---|
| 0xxxxxxx | ASCII 單字節 | 直接輸出字符 |
| 110xxxxx | 雙字節首位 | 記錄 5 位,期待 1 個續位 |
| 1110xxxx | 三字節首位 | 記錄 4 位,期待 2 個續位 |
| 11110xxx | 四字節首位 | 記錄 3 位,期待 3 個續位 |
| 10xxxxxx | 續位 | 累加 6 位,滿足期待數則組合輸出字符 |
此實現兼容 1-6 字節 UTF-8 序列,覆蓋所有 Unicode 字符平面。
現代項目優先推薦使用 JDK 內建 API,自動處理邊界情況:
// 編碼
String encoded = URLEncoder.encode("你 好", StandardCharsets.UTF_8.toString());
// 解碼
String decoded = URLDecoder.decode("%E4%BD%A0%20%E5%A5%BD", StandardCharsets.UTF_8.toString());
注意:URLEncoder 會將空格編碼為 +,而手動實現中 + 也被視為空格,兩者行為一致。
%25XX,解碼時需確認是否為多層編碼。+、%、#、& 等在 URL 中有特殊含義,需按規範編碼。// 單元測試片段
assertEquals("%E4%BD%A0", URLtoUTF8.toUtf8String("你"));
assertEquals("你 好", URLtoUTF8.unescape("%E4%BD%A0%20%E5%A5%BD"));
assertEquals("特殊字符+&#", URLtoUTF8.unescape(URLtoUTF8.toUtf8String("特殊字符+&#")));