先講個故事
美國人發明了計算機,然後他們覺得計算機也就a-z A-Z 0-9這些字符。
然後他們就發明了ASCII碼。一共也就256個,十六進制就是0X00-0XFF
後來各個國家都覺得計算機這東西不錯,然後也都想在計算機上顯示自己國家的語言。
然後各國就都出現了自己國家的字符集。當然他們也要兼容ASCII碼。中國就出現了GBK/GBK2312.
但各個國家的語言都定義了自己的字符集,不兼容怎麼辦?
然後人們就像發明一個能包含所有語言文字的編碼,就出現了UNICODE編碼。
“unicode是2個字節。 這一標準的2字節形式通常稱作UCS-2。然而,受制於2字節數量的限制,UCS-2只能表示最多65536個字符。Unicode的4字節形式被稱爲UCS-4或UTF-32,能夠定義Unicode的全部擴展,最多可定義100萬個以上唯一字符
但是當時人太窮,買不起太大的硬盤,用UCS-4太浪費存儲空間。然後又發明了UTF-8,UTF-8可以通過運算與UNICODE互轉。而且UTF-8是可變長的。比如A、B、C這些簡單的用一個字節就能表示了。
那麼現在問題又來了。我們用GBK編碼存儲的文件怎麼顯示呢?那麼就需要一個對照表,比如有一張表紀錄了所有GBK編碼表和UNICODE之間的對應關係。當然這個表僅僅紀錄了中文。也就是說,GBK裏面所有的編碼都能對應到UNICODE,但UNICODE的編碼未必能在GBK中找到。
結論
UNICODE轉UTF-8只需要運算就可以直接互轉
UNICODE與GBK轉換需要一張編碼對應表
屏幕上顯示的、鍵盤上輸入的其實都是UNICODE編碼的字符串,但是當保存成文件時候就會根據設定的字符集進行轉換了。當打開文件時也一樣,先要知道文件具體的字符集然後通過對應表(GBK)或者數學運算(UTF-8)轉換成UNICODE
UTF-8編碼原理
UTF-8採用變長方式存儲,也就是要看對應的UNICODE編碼是多少來決定採用多少字節表示,下表中X表示可以填入數據的位置
1字節 0xxxxxxx //可填入1-7位
2字節 110xxxxx 10xxxxxx //可填入 8-11位 。1-7用上面的就可以了,沒必要浪費空間
3字節 1110xxxx 10xxxxxx 10xxxxxx //可填入12-16位 1-11用上面的就可以了,沒必要浪費空間
4字節 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
5字節 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
6字節 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
舉個例子
"漢"這個字的UNICODE編碼是6C49 二進制01101100 01001001 需要16位 ,那麼根據上面的表格可以確定要用3字節表示
將0110 110001001001從左至右填入下面的方塊中
1110▢▢▢▢ 10▢▢▢▢▢▢ 10▢▢▢▢▢▢
11100110 10110001 10001001
轉換成16進制 E6B189
測試
百度找個unicode在線工具可以試試“漢”轉碼後是不是 6C49
在用JAVA代碼測試一下看看“漢”轉UTF-8是不是E6B189,網上也有漢字轉UTF-8的工具。可是編碼和UNICODE一樣。我也不知道爲什麼。
下面是JAVA代碼示例
public class Test1 {
public static String bytesToHexString(byte[] bArr) {
StringBuffer sb = new StringBuffer(bArr.length);
String sTmp;
for (int i = 0; i < bArr.length; i++) {
sTmp = Integer.toHexString(0xFF & bArr[i]);
if (sTmp.length() < 2)
sb.append(0);
sb.append(sTmp.toUpperCase()+" ");
}
return sb.toString();
}
public static void main(String[] arg)
{
try {
System.out.println(Test1.bytesToHexString("漢".getBytes("UTF-8")));
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
輸出 :E6 B1 89
下面介紹具體應用
說明UTF8對應漢字通常需要3字節,GBK對應漢字需要2字節
首先要說明String,String就是可見字符,是可以打印到屏幕的,因此他就是轉換後的UNICODE碼, 沒有具體字符集不是GBK也不是UTF-8
先說UTF-8轉成GBK,再轉會到UTF-8
public class Test1 {
public static void main(String[] arg)
{
try {
String s = "測試";//UNICODE編碼
byte[] b = s.getBytes("UTF-8");//轉成UTF-8編碼 6個字節長 E6 B5 8B E8 AF 95
System.out.println(Test1.bytesToHexString(b));//輸出 E6 B5 8B E8 AF 95
String sU2F = new String(b,"GBK");//本來是UTF8編碼 非告訴系統這個是GBK編碼。系統會兩兩組合(GBK是雙字節存儲的)到對照表中找尋對應的字符 (E6 B5) (8B E8) (AF 95)
System.out.println(sU2F);//顯示亂碼 ,因爲對應的位置一定是錯的 而且之前的兩個漢字變成了3個漢字 顯示:嫺嬭瘯,但是數據沒有丟失,如果獲取嫺嬭瘯的GBK編碼其仍然是E6 B5 8B E8 AF 95
String sF2U = new String(sU2F.getBytes("GBK"),"UTF-8");//重新創建String ,告訴系統 這個byte[] 是UTF-8編碼 ,編碼正確 字符串顯示也就正確了。
System.out.println(sF2U);//輸出測試
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] arg)
{
try {
String s = "測試啊";//UNICODE編碼
byte[] b = s.getBytes("UTF-8");//轉成UTF-8編碼 9個字節長度 E6 B5 8B E8 AF 95 E5 95 8A
System.out.println(Test1.bytesToHexString(b));//輸出 E6 B5 8B E8 AF 95 E5 95 8A
String sU2F = new String(b,"GBK");//本來是UTF8編碼 非告訴系統這個是GBK編碼。系統會兩兩組合(GBK是雙字節存儲的)去GBK編碼表中尋找對應漢字 (E6 B5) (8B E8) (AF 95)(E5 95) (8A XX)
System.out.println(sU2F);//顯示亂碼 , 而且之前的3個字變成了5個字 而且還引入了後面一個未知字節 顯示:嫺嬭瘯鍟�
String sF2U = new String(sU2F.getBytes("GBK"),"UTF-8");//重新創建String ,告訴系統 這個byte[] 是UTF-8編碼 ,前面都正確,但最後因爲引入了未知字節,最後一個字會有問題
System.out.println(sF2U);//輸出亂碼:測試�?
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
下面介紹GBK轉UTF-8
public static void main(String[] arg)
{
try {
String s = "測試";//UNICODE編碼
byte[] b = s.getBytes("GBK");//轉成GBK編碼 4個字節長度 B2 E2 CA D4
System.out.println(Test1.bytesToHexString(b));//輸出 B2 E2 CA D4
String sU2F = new String(b,"UTF-8");//本來是GBK編碼 非告訴系統這個是UTF-8編碼。
System.out.println(Test1.bytesToHexString(sU2F.getBytes("UTF-8")));//EF BF BD EF BF BD EF BF BD EF BF BD
//本來4個字節怎麼就變成了12個字節了?
//因爲UTF-8是有編碼規則的,上面已經介紹過。發現不符合規則 就轉成 EF BF BD ,一共4個字節就是12個字節了
System.out.println(sU2F);//顯示亂碼���� ,EF BF BD 對應UTF-8就是�
//由於此時的編碼是沒對應上,都強制轉換成 EF BF BD 就等於編碼已經完全丟失,因此再轉換回GBK也是錯誤的
//之前GBK轉UTF-8 是對應到了。雖然是錯的,但在對照表中找到了。所以並沒有丟失數據。
String sF2U = new String(sU2F.getBytes("UTF-8"),"GBK");
System.out.println(sF2U);//輸出亂碼:錕斤拷錕斤拷 當把GBK編碼的byte當成UTF8處理 大多數情況都是無法對應的。也就是說基本都是 錕斤拷
} catch (UnsupportedEncodingException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
結論
一切byte[] 都要知道他本身的編碼 不然基本都會出錯,比如 讀取文件 接收網絡傳輸的信息時都需要明確知道字符串的編碼格式。
一切String 都是UNICODE編碼 不是UTF-8 也不是GBK的
如果把GBK編碼的byte[] 當成 UTF-8 一定會丟失數據,而且沒有辦法轉回來
如果把UTF-8編碼的byte[] 當成GBK 處理 就要看運氣了。如果偶數中文還可以轉回來,基數中文就會在最後一個漢字引入了未知字節,即使轉換回來也一定是亂碼