字符集原理GBK與UTF8與UNICODE

先講個故事

美國人發明了計算機,然後他們覺得計算機也就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 處理 就要看運氣了。如果偶數中文還可以轉回來,基數中文就會在最後一個漢字引入了未知字節,即使轉換回來也一定是亂碼

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章