天天看點

字元編碼ANSI、ASCII、GB2312、GBK、GB18030、UNICODE、UTF-8小結1.英語字元編碼ASCII2.中文編碼GB2312、GBK、GB180303.統一碼UNICODE4.UNICODE傳輸标準之UTF-85.Little endian和Big endian的差別與識别6.中文window作業系統編碼總結7.編碼的互相轉換

      編碼和解碼可以了解成二進制和字元(廣義的字元,包括漢字等)的映射表,編碼即從字元映射至二進制,解碼則為逆過程。

1.英語字元編碼ASCII

   開始計算機隻在美國用。8位元組一共可以組合出256(2的8次方)種不同的狀态。美國人把其中的編号從0開始的32種狀态分别規定了特殊的用途,一但終端、列印機遇上約定好的這些位元組被傳過來時,就要做一些約定的動作。遇上00x10, 終端就換行,遇上0x07, 終端就向人們嘟嘟叫,例好遇上0x1b, 列印機就列印反白的字,或者終端就用彩色顯示字母。他們看到這樣很好,于是就把這些0x20以下的位元組狀态稱為"控制碼"。

  他們又把所有的空格、标點符号、數字、大小寫字母分别用連續的位元組狀态表示,一直編到了第127号,這樣計算機就可以用不同位元組來存儲英語的文字了。大家看到這樣,都感覺很好,于是大家都把這個方案叫做 ANSI 的"ASCII"編碼(American Standard Code for Information Interchange,美國資訊互換标準代碼)。當時世界上所有的計算機都用同樣的ASCII方案來儲存英文文字。

    随着世界各地的都開始使用計算機,但是很多國家用的不是英文,他們的字母裡有許多是ASCII裡沒有的,為了可以在計算機儲存他們的文字,他們決定采用127号之後的空位來表示這些新的字母、符号,還加入了很多畫表格時需要用下到的橫線、豎線、交叉等形狀,一直把序号編到了最後一個狀态255。從128到255這一頁的字元集被稱"擴充字元集"。

    簡而言之,ASCII碼一共規定了128個字元的編碼,比如空格“SPACE”是32(二進制00100000),大寫的字母A是65(二進制01000001)。這128個符号(包括32個不能列印出來的控制符号),隻占用了一個位元組的後面7位,最前面的1位統一規定為0。

2.中文編碼GB2312、GBK、GB18030

     GB2312:GB2312 是對 ASCII 的中文擴充。中國人得到計算機時,已經沒有可以利用的位元組狀态來表示漢字,況且有6000多個常用漢字需要編碼。我們規定:一個小于127的字元的意義與原來相同(同ASCII),但兩個大于127的位元組連在一起時,就表示一個漢字,其中高位元組範圍規定為從0xA1—0xF7,低位元組範圍規定為從0xA1—0xFE。這套編碼中,不僅包括了7000多個常用簡體漢字,還包括數學符号、羅馬希臘字母、日文假名。 ASCII 裡本來就有的數字、标點、字母都統統重新編了兩個位元組長的編碼,即所謂的"全角"字元,而原來在127号以下的那些就叫"半角"字元了。GB2312使用兩個位元組表示一個漢字,是以理論上最多可以表示256x256=65536個符号。

     GBK 标準:由于中國仍舊有許多偏僻字無法表示。後來規定:隻要第一個位元組是大于127就固定表示這是一個漢字的開始,不管後面跟的是不是擴充字元集裡的内容。即形成GBK标準,GBK 包括了 GB2312 的所有編碼,同時又增加了近20000個新的漢字(包括繁體字)和符号。GBK标準包括拉丁字母、漢字、日文、韓文、俄文,不包括俄語、葡萄牙語。

     GB18030标準:擴充至少數民族字母符号。

      這一系列标準稱為 "DBCS"(Double Byte Charecter Set 雙位元組字元集)。其最大的特點是2位元組長的漢字字元和1位元組長的英文字元并存于同一套編碼方案裡。以前的程式程式為了支援中文處理,必須要注意字串裡的每一個位元組的值,如果這個值是大于127的,那麼就認為一個雙位元組字元集裡的字元出現了。注意,GB類的漢字編碼與後文的Unicode和UTF-8是毫無關系。

3.統一碼UNICODE

    各個國家都像中國這樣搞出一套自己的編碼标準,結果互相之間誰也不懂誰的編碼,誰也不支援别人的編碼。即,盡管二進制相同,由于映射表不一樣,換成他人的标準解出來就是亂碼。雖然英語用128個符号編碼就夠了,但是用來表示其他語言,128個符号是不夠的。比如,在法語中,字母上方有注音符号,它就無法用ASCII碼表示。于是,一些歐洲國家就決定,利用位元組中閑置的最高位編入新的符号。比如,法語中的é的編碼為130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符号。但是,這裡又出現了新的問題。不同的國家有不同的字母,是以,哪怕它們都使用256個符号的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (ג),在俄語編碼中又會代表另一個符号。

      ISO 解決了這個問題。他們采用的方法很簡單:廢了所有的地區性編碼方案,重新搞一個包括了地球上所有文化、所有字母和符号的編碼!這種它"Universal Multiple-Octet Coded Character Set",簡稱 UCS, 俗稱 "UNICODE"。

    UNICODE 開始制訂時,計算機的存儲器容量極大地發展了,空間再也不成為問題了。于是 ISO 就直接規定必須用兩個位元組,也就是16位來統一表示所有的字元。對于ASCII裡的那些“半角”字元(1-127),UNICODE 包持其原編碼不變,隻是将其長度由原來的8位擴充為16位,而其他文化和語言的字元則全部重新統一編碼。由于"半角"英文符号隻需要用到低8位,是以其高8位永遠是0。是以這種大氣的方案在儲存英文文本時會多浪費一倍的空間。

      但是,UNICODE 在制訂時沒有考慮與全球任何一種現有的編碼方案保持相容,這使得 GBK 與UNICODE 在漢字的内碼編排上完全是不一樣的。但由于其對于所有語言的通用性,得到了迅速推廣,比如MS 和WINDOWS 作業系統。

      Unicode是一個很大的集合,現在的規模可以容納100多萬個符号。每個符号的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字“嚴”。

4.UNICODE傳輸标準之UTF-8

     一般而言,UNICODE 用兩個位元組來表示為一個字元,他總共可以組合出65535不同的字元,這大概已經可以覆寫世界上所有文化的符号。如果還不夠也沒有關系,ISO已經準備了UCS-4方案,說簡單了就是四個位元組來表示一個字元,可以組合出21億個不同的字元。

     但是注意到Unicode隻是一個符号集(映射表),它隻規定了符号的二進制代碼,卻沒有規定這個二進制代碼應該如何存儲。比如,漢字“嚴”的unicode是4E25(100111000100101)表示至少需要2個位元組。這裡就有兩個嚴重的問題:

       1.如何才能差別unicode和ascii?計算機怎麼知道三個位元組表示1個符号而不是分别表示3個符号
         2.存儲浪費。英文字母隻用一個位元組表示就夠了,unicode可能會使用2個以上的位元組表示英文字母,大量的0浪費了存儲。      

     在網際網路普及的背景下,出現了傳輸unicode的UTF标準(Unicode Transformation Format)。例如普遍使用的UTF-8(Unicode Transformation Format -8 bit)就是每次8個位傳輸資料,而UTF-16就是每次16個位。為了傳輸時的可靠性,從UNICODE到UTF要過一些算法和規則來轉換。Unicode碼轉換成UTF-8傳輸碼規則如下:

              Unicode符号範圍 | UTF-8編碼方式
                   (十六進制) | (二進制)
          --------------------+---------------------------------------------
          0000 0000-0000 007F | 0xxxxxxx
          0000 0080-0000 07FF | 110xxxxx 10xxxxxx
          0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
          0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx      

    舉例而言  "漢"字的Unicode編碼是6C49(範圍0800-FFFF,二進制0110 1100 0100 1001),使用模闆:1110xxxx 10xxxxxx 10xxxxxx轉成UTF-8為E6 B1 89(1110-0110 10-110001 10-001001)。同樣,漢字“嚴”的unicode是4E25(100111000100101),對應的UTF-8編碼是E4B8A5(“11100100 10111000 10100101”)。

     例子:在 windows 的記事本裡建立一個檔案,輸入"聯通"兩個字之後,儲存,關閉,然後再次打開,你會發現這兩個字已經消失了,代之的是幾個亂碼!      

   其實這是GB2312編碼與UTF8編碼沖突導緻的。建立一個文本檔案時,記事本的編碼預設是ANSI,如果你在ANSI的編碼下輸入漢字,那麼他實際使用GB系列的漢字編碼方式,在這種編碼下,"聯通"的二進制碼是C1AA (1100 0001 1010 1010),CDA8(1100 1101 1010 1000)。注意到,“聯通”的GB編碼都符合UTF-8的第二種模闆("110***** 10******"),再次打開記事本時,記事本就誤認為這是一個UTF-8編碼的檔案。會将其轉回統一碼顯示,其統一碼Unicode為 006A 0368("0000 0000 0110 1010"),前者是小寫的字母"j",而後者什麼也不是。這就是顯示為亂碼的原因。而如果你在"聯通"之後多輸入幾個字,其他的字的編碼不見得又恰好是110和10開始的位元組,這樣再次打開時,記事本就不會堅持這是一個UTF-8編碼的檔案,而會用ANSI的方式解讀,這時亂碼又不出現了。

5.Little endian和Big endian的差別與識别

     這兩個古怪的名稱來自英國作家斯威夫特的《格列佛遊記》。在該書中,小人國裡爆發了内戰,戰争起因是人們争論,吃雞蛋時究竟是從大頭(Big-Endian)敲開還是從小頭(Little-Endian)敲開。為了這件事情,前後爆發了六次戰争,一個皇帝送了命,另一個皇帝丢了王位。

     在網絡裡傳遞資訊時有一個很重要的問題,就是對于資料高低位的解讀方式,一些計算機是采用低位先發送的方法,例如我們PC機采用的 INTEL 架構,而另一些是采用高位先發送的方式,如網絡中交換資料。為了便于傳輸,采用标志符的方法。具體而言二者差別如下。

   Big-Endian俗稱大頭存儲:記憶體從最低位址開始按順序存放,低位位址存放資料高位位元組。UFT 格式文本流傳送标志位FEFF 
   Little-Endian俗稱小頭存儲:記憶體從最低位址開始按順序存放,低位位址存放資料低位位元組。UFT格式文本流傳送标志位FFFE      

6.中文window作業系統編碼總結

     1)ANSI是預設的編碼方式。對于英文檔案是ASCII編碼,對于簡體中文檔案是GB2312編碼(隻針對Windows簡體中文版,如果是繁體中文版會采用Big5碼)。英文字母占1個位元組,漢字占2個位元組

     2)Unicode編碼(預設的little endian)具體指UCS-2編碼方式,即直接用2個位元組的Unicode碼。英文和漢字都是2個位元組存儲。

     3)Unicode big endian編碼與上一個選項相對應。英文和漢字都是2個位元組存儲。

     4)UTF-8編碼,也就是上一節談到的編碼方法。一個英文字母占1個位元組,一個漢字占3個位元組

  一般而言,ANSI占用存儲空間最小,也就是為什麼預設使用的原因。

7.編碼的互相轉換

public class Tranform {
	public static void main(String args[]) throws Exception {
        String chineseStr="我們";
        Transform t=new Transform();
        System.out.println(t.Chinese2UTF_8(chineseStr));
        System.out.println(t.Chinese2GBK(chineseStr));
        System.out.println(t.GBK2Chinese(t.Chinese2GBK(chineseStr)));
	}
}

 class Transform {
  //中文轉換成UTF-8編碼(16進制字元串),每個漢字3個位元組
  public String Chinese2UTF_8(String chineseStr)throws Exception {
	StringBuffer utf8Str = new StringBuffer();
	byte[] utf8Decode = chineseStr.getBytes("utf-8");
	for (byte b : utf8Decode) 
		utf8Str.append(Integer.toHexString(b&0xFF));
	return utf8Str.toString().toUpperCase();
  }	
	
  //中文轉換成GBK碼(16進制字元串),每個漢字2個位元組
  public String Chinese2GBK(String chineseStr)throws Exception {
	StringBuffer GBKStr = new StringBuffer();
	byte[] GBKDecode = chineseStr.getBytes("gbk");
	for (byte b : GBKDecode) 
		GBKStr.append(Integer.toHexString(b&0xFF));
	return GBKStr.toString().toUpperCase();
	}
	
	
  //16進制GBK字元串轉換成中文
  public String GBK2Chinese(String GBKStr)throws Exception{
	byte[] b = HexString2Bytes(GBKStr);
	String chineseStr = new String(b, "gbk");//輸入參數為位元組數組
	return chineseStr;
   }
  
  //把16進制字元串轉換成位元組數組
  public byte[] HexString2Bytes(String hexStr) {
	 byte[] b = new byte[hexStr.length() / 2];
	 for (int i = 0; i < b.length; i++) 
       b[i]=(byte) Integer.parseInt(hexStr.substring(2*i,2*i+2),16);
	return b;
  }

	
   //把位元組數組轉換成16進制字元串
   public static final String bytesToHexString(byte[] byteArray){
	 StringBuffer hexStr = new StringBuffer(byteArray.length*2);
	 for (int i = 0; i < byteArray.length; i++) {
		 String sTemp= Integer.toHexString(0xFF& byteArray[i]);
		 int j=0;
	     while(j<2-sTemp.length())
	    	 {sTemp="0"+sTemp;j++;}
	     hexStr.append(sTemp.toUpperCase());
	   }
	  return hexStr.toString();
	}

 }
           

注意:

str.getBytes("UTF-8"); 的意思是以UTF-8的編碼取得位元組 
   new String(XXX,"UTF-8"); 的意思是以UTF-8的編碼生成字元串
   
   public static String toUtf_8(String str) { 
               return new String(str.getBytes("UTF-8"),"UTF-8");//字元串轉成utf8
   }
           

本文參考:

1.http://blog.csdn.net/iefreer/article/details/4836844

2.http://bbs.csdn.net/topics/360164725

3.http://www.hackbase.com/tech/2009-03-16/51640_1.html

4.http://www.ruanyifeng.com/blog/2007/10/ascii_unicode_and_utf-8.html

5. 各種編碼UNICODE、UTF-8、ANSI、ASCII、GB2312、GBK詳解

6.英語音标和Unicode