2016-12-27
對字符編碼時的規則
通常如果一樣東西需要編碼,說明這樣東西並不適合傳輸。原因多種多樣,如Size過大,包含隱私數據。
對於Url來說,之所以要進行編碼,一個是
因為Url中有些字符會引起歧義。
例如Url參數字符串中使用key=value鍵值對這樣的形式來傳參,鍵值對之間以&符號分隔,如/s?q=abc&ie=utf-8。
如果你的value字符串中包含了=或者&,那么勢必會造成接收Url的服務器解析錯誤,因此必須將引起歧義的&和=符號進行轉義,也就是對其進行編碼。
另外,
Url的編碼格式采用的是ASCII碼,而不是Unicode(包含中文),這也就是說你不能在Url中包含任何非ASCII字符,例如中文。否則如果客戶端瀏覽器和服務端瀏覽器支持的字符集不同的情況下,中文可能會造成問題。
編碼時的規則:
- 【字母】字符 "a" 到 "z"、"A" 到 "Z" 和【數字】字符 "0" 到 "9" 保持不變
- 特殊字符【.】【-】【*】【_】保持不變
- 空格字符 【 】轉換為一個加號【+】
- 所有其他字符都是不安全的,因此首先使用一些編碼機制將它們轉換為【一個或多個】字節,然后每個字節用一個包含 3 個字符的字符串【%xy】表示,其中 xy 為該字節的兩位十六進制表示形式。
- 可以指定對這些字符進行解碼的編碼機制,或者如果未指定的話,則使用平台的默認編碼機制。
例如,使用 UTF-8 編碼機制,字符串【ü@】將轉換為【%C3%BC%40】,因為在 UTF-8 中,字符【ü】編碼為兩個字節【
0x
C3和
0x
BC】,字符【@】編碼為一個字節【0x40】。
解碼時的規則:
- 將把【%xy】格式的子序列視為【一個字節】,其中 xy 為 8 位的兩位十六進制表示形式。
- 然后,所有連續包含一個或多個這些字節序列的子字符串,將被其編碼可生成這些連續字節的字符所代替。
測試代碼
public class Test {
public static void main(String[] args) throws Exception {
String enc = "UTF-8";
test("aA0 .-*_", enc);//編碼【aA0+.-*_】解碼【aA0 .-*_】
test("@", enc);//編碼【%40】解碼【@】
test("ü", enc);//編碼【%C3%BC】解碼【ü】
test("白", enc);//編碼【%E7%99%BD】解碼【白】
test("白", "GBK");//編碼【%B0%D7】解碼【白】
}
/**
* 測試編解碼
* @param str 原始字符串
* @param enc 對字符串進行URLEncoder和URLDecoder時使用的編碼規則
* @throws UnsupportedEncodingException
*/
public static void test(String str, String enc) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(str, enc);//推薦 UTF-8。如果未指定,則使用平台的默認編碼
String decode = URLDecoder.decode(encode, enc);//編碼和解碼必須使用同一套碼表,否則解碼時可能亂碼
System.out.println("原始字符串【" + str + "】編碼后【" + encode + "】解碼后【" + decode + "】");
//獲取通過UTF8碼表編碼的字符串的字節數組
byte[] bytes = str.getBytes(enc);
System.out.println(" 原始字符串對應的字節數組" + Arrays.toString(bytes));
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
//手動對【每個字節】都進行上面【類似URLEncoder】編碼規則的算法進行編碼(即使字母和數字也進行了編碼)
sb.append(Integer.toHexString(b & 0xFF).toUpperCase() + " ");
}
System.out.println(" 對每個字節都進行編碼【" + sb.toString().trim() + "】");
}
//源碼
public static String decode(String s, String enc) throws UnsupportedEncodingException {
boolean needToChange = false;
int numChars = s.length();//字符數
StringBuffer sb = new StringBuffer(numChars > 500 ? numChars / 2 : numChars);
int i = 0;
if (enc.length() == 0) throw new UnsupportedEncodingException("URLDecoder: empty string enc parameter");
char c;
byte[] bytes = null;
while (i < numChars) {
c = s.charAt(i);//逐個遍歷所有字符
switch (c) {
case '+':
sb.append(' ');
i++;
needToChange = true;
break;
case '%':
try {
if (bytes == null) bytes = new byte[(numChars - i) / 3];// (numChars-i)/3 is an upper bound上線 for the number of remaining剩下的 bytes
int pos = 0;
while (((i + 2) < numChars) && (c == '%')) {
int v = Integer.parseInt(s.substring(i + 1, i + 3), 16);//16進制
if (v < 0) throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - negative value");
bytes[pos++] = (byte) v;
i += 3;
if (i < numChars) c = s.charAt(i);
}
// A trailing, incomplete不完全的 byte encoding such as "%x" will cause an exception to be thrown
if ((i < numChars) && (c == '%')) throw new IllegalArgumentException("URLDecoder: Incomplete trailing escape (%) pattern");
sb.append(new String(bytes, 0, pos, enc));
} catch (NumberFormatException e) {
throw new IllegalArgumentException("URLDecoder: Illegal hex characters in escape (%) pattern - " + e.getMessage());
}
needToChange = true;
break;
default:
sb.append(c);
i++;
break;
}
}
return (needToChange ? sb.toString() : s);
}
}
