【GBK轉UTF-8】
在很多論壇、網上經常有網友問“ 為什么我使用 new String(tmp.getBytes("ISO-8859-1"), "UTF-8") 或者 new String(tmp.getBytes("ISO-8859-1"), "GBK")可以得到正確的中文,但是使用 new String(tmp.getBytes("GBK"), "UTF-8") 卻不能將GBK轉換成UTF-8呢?”
參考前面的【Java基礎專題】編碼與亂碼(03)----String的toCharArray()方法測試 一文,我們就知道原因了。因為如果客戶端使用GBK、UTF-8編碼,編碼后的字節經過ISO-8859-1傳輸,再用原來相同的編碼方式進行解碼,這個過程是“無損的轉換”---- 因為原始和最終的編碼方式相同。
但是如果客戶端使用GBK編碼,到了服務器端要轉換成UTF-8,或者相反的過程。想一想,字節還是那些字節,但是編碼的規則變了。原來GBK編碼后的4個字節要用UTF-8的每個字符3個字節的規則編碼,怎么能不亂碼呢?
所以從現在開始,不要再犯這種錯誤了。new String(tmp.getBytes("GBK"), "UTF-8") 這個過程,JVM內部是不會幫你自動對字節進行擴展以適應UTF-8的編碼的。正確的方法應該是根據UTF-8的編碼規則進行字節的擴充,即手動從2個字節變成3個字節,然后再轉換成十六進制的UTF-8編碼。
在這個專題的第一篇文章【Java基礎專題】編碼與亂碼(01)---編碼基礎 開頭,我們就已經介紹了這個規則:
①得到每個字符的2進制GBK編碼
②將該16進制的GBK編碼轉換成2進制的字符串(2個字節)
③分別在字符串的首位插入110,在第9位插入10,在第17位插入10三個字符串,得到3個字節
④將這3個字節分別轉換成16進制編碼,得到最終的UTF-8編碼。
下面給出一個從網絡上得到的Java轉碼方法,原文鏈接見:http://jspengxue.javaeye.com/blog/40781。下面的代碼做了小小的修改
package example.encoding;
/**
* The Class CharacterEncodeConverter.
*/
public class CharacterEncodeConverter {
/**
* The main method.
*
* @param args the arguments
*/
public static void main(String[] args) {
try {
CharacterEncodeConverter convert = new CharacterEncodeConverter();
byte[] fullByte = convert.gbk2utf8("中文");
String fullStr = new String(fullByte, "UTF-8");
System.out.println("string from GBK to UTF-8 byte: " + fullStr);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* Gbk2utf8.
*
* @param chenese the chenese
*
* @return the byte[]
*/
public byte[] gbk2utf8(String chenese) {
// Step 1: 得到GBK編碼下的字符數組,一個中文字符對應這里的一個c[i]
char c[] = chenese.toCharArray();
// Step 2: UTF-8使用3個字節存放一個中文字符,所以長度必須為字符的3倍
byte[] fullByte = new byte[3 * c.length];
// Step 3: 循環將字符的GBK編碼轉換成UTF-8編碼
for (int i = 0; i < c.length; i++) {
// Step 3-1:將字符的ASCII編碼轉換成2進制值
int m = (int) c[i];
String word = Integer.toBinaryString(m);
System.out.println(word);
// Step 3-2:將2進制值補足16位(2個字節的長度)
StringBuffer sb = new StringBuffer();
int len = 16 - word.length();
for (int j = 0; j < len; j++) {
sb.append("0");
}
// Step 3-3:得到該字符最終的2進制GBK編碼
// 形似:1000 0010 0111 1010
sb.append(word);
// Step 3-4:最關鍵的步驟,根據UTF-8的漢字編碼規則,首字節
// 以1110開頭,次字節以10開頭,第3字節以10開頭。在原始的2進制
// 字符串中插入標志位。最終的長度從16--->16+3+2+2=24。
sb.insert(0, "1110");
sb.insert(8, "10");
sb.insert(16, "10");
System.out.println(sb.toString());
// Step 3-5:將新的字符串進行分段截取,截為3個字節
String s1 = sb.substring(0, 8);
String s2 = sb.substring(8, 16);
String s3 = sb.substring(16);
// Step 3-6:最后的步驟,把代表3個字節的字符串按2進制的方式
// 進行轉換,變成2進制的整數,再轉換成16進制值
byte b0 = Integer.valueOf(s1, 2).byteValue();
byte b1 = Integer.valueOf(s2, 2).byteValue();
byte b2 = Integer.valueOf(s3, 2).byteValue();
// Step 3-7:把轉換后的3個字節按順序存放到字節數組的對應位置
byte[] bf = new byte[3];
bf[0] = b0;
bf[1] = b1;
bf[2] = b2;
fullByte[i * 3] = bf[0];
fullByte[i * 3 + 1] = bf[1];
fullByte[i * 3 + 2] = bf[2];
// Step 3-8:返回繼續解析下一個中文字符
}
return fullByte;
}
}
最終的測試結果是正確的:string from GBK to UTF-8 byte: 中文。
但是這個方法并不是完美的!要知道這個規則只對中文起作用,如果傳入的字符串中包含有單字節字符,如a+3中文,那么解析的結果就變成:string from GBK to UTF-8 byte: ?????????中文了。為什么呢?道理很簡單,這個方法對原本在UTF-8中應該用單字節表示的數字、英文字符、符號都變成3個字節了,所以這里有9個?,代表被轉換后的a、+、3字符。
所以要讓這個方法更加完美,最好的方法就是加入對字符Unicode區間的判斷
UCS-2編碼(16進制)
UTF-8 字節流(二進制)
0000 - 007F
0xxxxxxx
0080 - 07FF
110xxxxx 10xxxxxx
0800 - FFFF
1110xxxx 10xxxxxx 10xxxxxx
漢字的Unicode編碼范圍為\u4E00-\u9FA5 \uF900-\uFA2D,如果不在這個范圍內就不是漢字了。
【UTF-8轉GBK】
道理和上面的相同,只是一個逆轉的過程,不多說了
但是最終的建議還是:能夠統一編碼就統一編碼吧!要知道編碼的轉換是相當的耗時的工作
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。