一、ASCII碼
在計算機內部,所有的信息最終都是一個二進制值,而每一個二進制位有0和1兩種狀態,所以八個二進制位就可以表示出256中不同的狀態,也就是說,一個字節可以表示256中不同的狀態,每一個狀態對應一個符號,就是256個符號。
上個世紀60年代,美國制定了一套字符編碼,對英語字符與二進制位之間的關系,做了統一規定。這被稱為 ASCII 碼,一直沿用至今。
ASCII碼規定了128個字符的編碼,這128個符號只占用一個字節的后面7位,最前面的一位統一規定為0。
二、非ASCII碼
英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。比如,在法語中,字母上方有注音符號,它就無法用 ASCII 碼表示。于是,一些歐洲國家就決定,利用字節中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。
但是,這里又出現了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0--127表示的符號是一樣的,不一樣的只是128--255的這一段。
至于亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節只能表示256種符號,肯定是不夠的,就必須使用多個字節表達一個符號。比如,簡體中文常見的編碼方式是 GB2312,使用兩個字節表示一個漢字,所以理論上最多可以表示 256 x 256 = 65536 個符號。
三、Unicode
世界上存在著多種編碼方式,同一個二進制數字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現亂碼。
可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那么亂碼問題就會消失。這就是 Unicode,就像它的名字都表示的,這是一種所有符號的編碼。
Unicode 當然是一個很大的集合,現在的規模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639
表示阿拉伯字母Ain
,U+0041
表示英語的大寫字母A
,U+4E25
表示漢字嚴
。具體的符號對應表,可以查詢unicode.org,或者專門的漢字對應表。
GBK和UTF-8都是用來序列化或存儲unicode編碼的數據的,但是分別是2種不同的格式; 他們倆除了格式不一樣之外,他們所關心的unicode編碼范圍也不一樣,utf-8考慮了很多種不同國家的字符,涵蓋整個unicode碼表,所以其存儲一個字符的編碼的時候,使用的字節長度也從1字節到4字節不等;而GBK只考慮中文——在unicode中的一小部分——的字符,的編碼,所以它算好了只要2個字節就能涵蓋到絕大多數常用中文(2個字節能表示6w多種字符),所以它存儲一個字符的時候,所用的字節長度是固定的;
四、Unicode的問題
最終Unicode的容量越來越大,其表示每一個符號的字節也越來越長。在這種情況下,如果用固定幾個字節來表示一個符號的話,則對于最開始的字節會出現空間浪費;如果用不定的字節來描述一個符號的話,那如何才能知道到底是用了幾個字節來表示一個符號?
五、解決辦法:UTF-8
UTF-8 就是在互聯網上使用最廣的一種 Unicode 的實現方式。其他實現方式還包括 UTF-16(字符用兩個字節或四個字節表示)和 UTF-32(字符用四個字節表示),不過在互聯網上基本不用。重復一遍,這里的關系是,UTF-8 是 Unicode 的實現方式之一。
UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節表示一個符號,根據不同的符號而變化字節長度。
UTF-8 的編碼規則很簡單,只有二條:
1)對于單字節的符號,字節的第一位設為0,后面7位為這個符號的 Unicode 碼。因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的。
2)對于n字節的符號(n > 1),第一個字節的前n位都設為1,第n + 1位設為0,后面字節的前兩位一律設為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。
跟據上表,解讀 UTF-8 編碼非常簡單。如果一個字節的第一位是0,則這個字節單獨就是一個字符;如果第一位是1,則連續有多少個1,就表示當前字符占用多少個字節。
下面,還是以漢字嚴為例,演示如何實現 UTF-8 編碼。
嚴的 Unicode 是4E25(100111000100101),根據上表,可以發現4E25處在第三行的范圍內(0000 0800 - 0000 FFFF),因此嚴的 UTF-8 編碼需要三個字節,即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,從嚴的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,嚴的 UTF-8 編碼是11100100 10111000 10100101,轉換成十六進制就是E4B8A5。
六、總結
應該說,每一種字符集本身就代表著一種編碼格式,(unicode也不例外)所以當采用這些字符集的時候,就是說使用了這種編碼格式。
七、getbyte方法
在java中,getBytes()方法如果不指定字符集,則得到的是一個操作系統默認的編碼格式的字節數組;如果指定字符集,則得到的是在指定字符集下的字節數組
實例:
package ObjectRef;
import java.io.UnsupportedEncodingException;
/**
* @author hankun
* @create 2017-06-26 20:28
*/
public class Test4 {
/**
*
* 1、Unicode是一種編碼規范,是為解決全球字符通用編碼而設計的,而UTF-8,UTF-16等是這種規范的一種實現。
2、java內部采用Unicode編碼規范,也就是支持多語言的,具體采用的UTF-16編碼方式。
3、不管程序過程中用到了gbk,iso8859-1等格式,在存儲與傳遞的過程中實際傳遞的都是Unicode編碼的數據,要想接收到的值不出現亂碼,就要保證傳過去的時候用的是A編碼,接收的時候也用A編碼來轉換接收。
4、如果雙方的file.encoding確保都相同,那就省事了,都默認轉了,但往往在不同項目交互時很多時候是不一致的,這個時候是必須要進行編碼轉換的。
5、無論如論轉換,java程序的數據都是要先和Unicode做轉換,這樣也就是能處理多語言字符集的原因了。底層保持了一致,只要在傳值和接值的時候也一致就肯定不會出現亂碼了。
* */
public static void main(String[] args) throws UnsupportedEncodingException {
String str = "中文字符";
System.out.println("original string---" + str);// 會正常輸出原始串
/**
*
* str.getBytes(); 如果括號中不寫charset,則采用的是Sytem.getProperty("file.encoding"),即當前文件的編碼方式,
*
* 很多人寫的是系統的默認編碼,通過代碼測試并非如此,實際得到的是文件的編碼方式*
*
* str.getBytes("charset");//指定charset,即將底層存儲的Unicode碼解析為charset編碼格式的字節數組方式
*
* String new_str=new String(str.getBytes("utf-8"),"gbk"));
*
* //將已經解析出來的字節數據轉化為gbk編碼格式的字符串,在內存中即為gbk格式的字節數組轉為Unicode去交互傳遞
*/
String new_str = new String(str.getBytes("utf-8"), "gbk");
/**
*
* 此時的輸出是亂碼,在UTF-8的file.encoding下輸出gbk格式的數據肯定是亂碼,但是new_str的確是gbk編碼式的
*
* 此時的亂碼源于encoding不符,但gbk格式的new_str本身數據并沒有問題,通過下面的轉換也可以看得出來
*/
System.out.println("new string----" + new_str);
String final_str = new String(new_str.getBytes("gbk"), "utf-8");// 此處的含意與最上邊的注釋是一致的參數含意
/**
*
*輸出是正常的,此時將gbk編碼格式的new_str字符串,用gbk這個charset去解析它,然后用utf-8再轉碼一次,
*
* 因為new_str確實是gbk格式的,才能經過utf-8編碼得到正常的數據顯示。
*/
System.out.println("final string---" + final_str);
}
}