聲明:本文轉(zhuǎn)載自:http://www.phome.asia/article/814.html
在計算機(jī)中,只有二進(jìn)制的數(shù)據(jù),不管數(shù)據(jù)是在內(nèi)存中,還是在外部存儲設(shè)備上。對于我們所看到的字符,也是以二進(jìn)制數(shù)據(jù)的形式存在的。不同字符對應(yīng)二進(jìn)制數(shù)的規(guī)則,就是字符的編碼。字符編碼的集合稱為字符集。
常用字符集
1. ASCII
在早期的計算機(jī)系統(tǒng)中,使用的字符非常少,這些字符包括26個英文字母、數(shù)字符號和一些常用符號(包括控制符號),對這些字符進(jìn)行編碼,用1個字節(jié)就足夠了(1個字節(jié)可以表示28=256種字符)。然而實(shí)際上,表示這些字符,只使用了1個字節(jié)的7位,這就是ASCII編碼1.ASCII
ASCII(American Standard Code
for Information Interchange,美國信息互換標(biāo)準(zhǔn)代碼),是基于常用的英文字符的一套電腦編碼系統(tǒng)。每一個ASCII碼與一個8位(bit)二進(jìn)制數(shù)對應(yīng)。其最高位是0,相應(yīng)的十進(jìn)制數(shù)是0~127。例如,數(shù)字字符“0”的編碼用十進(jìn)制數(shù)表示就是48。另有128個擴(kuò)展的ASCII碼,最高位都是1,由一些圖形和畫線符號組成。ASCII是現(xiàn)今最通用的單字節(jié)編碼系統(tǒng)。
ASCII用一個字節(jié)來表示字符,最多能夠表示256種字符。隨著計算機(jī)的普及,許多國家都將本地的語言符號引入到計算機(jī)中,擴(kuò)展了計算機(jī)中字符的范圍,于是就出現(xiàn)了各種不同的字符集。
2.ISO8859-1
因?yàn)?/span>ASCII碼中缺少£、ü和許多書寫其他語言所需的字符,為此,可以通過指定128以后的字符來擴(kuò)展ASCII碼。國際標(biāo)準(zhǔn)組織(ISO)定義了幾個不同的字符集,它們是在ASCII碼基礎(chǔ)上增加了其他語言和地區(qū)需要的字符。其中最常用的是ISO8859-1,通常叫做Latin-1。Latin-1包括了書寫所有西方歐洲語言不可缺少的附加字符,其中0~127的字符與ASCII碼相同。ISO 8859另外定義了14個適用于不同文字的字符集(8859-2到8859-15)。這些字符集共享0~127的ASCII碼,只是每個字符集都包含了128~255的其他字符。
3.GB2312和GBK
GB2312是中華人民共和國國家標(biāo)準(zhǔn)漢字信息交換用編碼,全稱《信息交換用漢字編碼字符集-基本集》,標(biāo)準(zhǔn)號為GB2312-80,是一個由中華人民共和國國家標(biāo)準(zhǔn)總局發(fā)布的關(guān)于簡化漢字的編碼,通行于中國大陸和新加坡,簡稱國標(biāo)碼。
因?yàn)橹形淖址麛?shù)量較多,所以采用兩個字節(jié)來表示一個字符,分別稱為高位和低位。為了和ASCII碼有所區(qū)別,中文字符的每一個字節(jié)的最高位都用1來表示。GB2312字符集是幾乎所有的中文系統(tǒng)和國際化的軟件都支持的中文字符集,也是最基本的中文字符集。它包含了大部分常用的一、二級漢字和9區(qū)的符號,其編碼范圍是高位0xa1-0xfe,低位也是0xa1-0xfe,漢字從0xb0a1開始,結(jié)束于0xf 7fe。
為了對更多的字符和符號進(jìn)行編碼,由前電子部科技質(zhì)量司和國家技術(shù)監(jiān)督局標(biāo)準(zhǔn)化司于1995年12月頒布了GBK(K是“擴(kuò)展”的漢語拼音第一個字母)編碼規(guī)范,在新的編碼系統(tǒng)里,除了完全兼容GB2312外,還對繁體中文、一些不常用的漢字和許多符號進(jìn)行了編碼。它也是現(xiàn)階段Windows和其他一些中文操作系統(tǒng)的默認(rèn)字符集,但并不是所有的國際化軟件都支持該字符集。不過要注意的是GBK不是國家標(biāo)準(zhǔn),它只是規(guī)范。GBK字符集包含了20 902個漢字,其編碼范圍是0x8140-0xfefe。
每個國家(或區(qū)域)都規(guī)定了計算機(jī)信息交換用的字符編碼集,這就造成了交流上的困難。想像一下,你發(fā)送一封中文郵件給一位遠(yuǎn)在西班牙的朋友,當(dāng)郵件通過網(wǎng)絡(luò)發(fā)送出去的時候,你所書寫的中文字符會按照本地的字符集GBK轉(zhuǎn) 換為二進(jìn)制編碼數(shù)據(jù),然后發(fā)送出去。當(dāng)你的朋友接收到郵件(二進(jìn)制數(shù)據(jù))后,查看信件時,會按照他所用系統(tǒng)的字符集,將二進(jìn)制編碼數(shù)據(jù)解碼為字符,然而由
于兩種字符集之間編碼的規(guī)則不同,導(dǎo)致轉(zhuǎn)換出現(xiàn)亂碼。這是因?yàn)椋诓煌淖址g,同樣的數(shù)字可能對應(yīng)了不同的符號,也可能在另一種字符集中,該數(shù)字沒 有對應(yīng)符號。
為了解決上述問題,統(tǒng)一全世界的字符編碼,由Unicode協(xié)會制定并發(fā)布了Unicode編碼。
4.Unicode
Unicode(統(tǒng)一的字符編碼標(biāo)準(zhǔn)集)使用0~65 535的雙字節(jié)無符號數(shù)對每一個字符進(jìn)行編碼。它不僅包含來自英語和其他西歐國家字母表中的常見字母和符號,也包含來自古斯拉夫語、希臘語、希伯來語、阿拉伯語和梵語的字母表。另外還包含漢語和日語的象形漢字和韓國的Hangul音節(jié)表。
目前已經(jīng)定義了40 000多個不同的Unicode字符,剩余25 000個空缺留給將來擴(kuò)展使用。其中大約20 000個字符用于漢字,另外11 000左右的字符用于韓語音節(jié)。Unicode中0~255的字符與ISO8859-1中的一致。
Unicode編碼對于英文字符采取前面加“0”字節(jié)的策略實(shí)現(xiàn)等長兼容。如“a”的ASCII碼為0x61,Unicode碼就為0x00,0x61。
5.UTF-8
使用Unicode編碼,一個英文字符要占用兩個字節(jié),在Internet上,大多數(shù)的信息都是用英文來表示的,如果都采用Unicode編碼,將會使數(shù)據(jù)量增加一倍。為了減少存儲和傳輸英文字符數(shù)據(jù)的數(shù)據(jù)量,可以使用UTF-8編碼。
UTF-8全稱是Eight-bit UCS
Transformation Format(UCS,Universal Character
Set,通用字符集,UCS是所有其他字符集標(biāo)準(zhǔn)的一個超集)。對于常用的字符,即0~127的ASCII字符,UTF-8用一個字節(jié)來表示,這意味著只包含7位ASCII字符的字符數(shù)據(jù)在ASCII和UTF-8兩種編碼方式下是一樣的。如果字符對應(yīng)的Unicode碼是0x0000,或在0x0080與0x007f之間,對應(yīng)的UTF-8編碼是兩個字節(jié),如果字符對應(yīng)的Unicode碼在0x0800與0xffff之間,對應(yīng)的UTF-8編碼是三個字節(jié)。因?yàn)橹形淖址?/span>Unicode編碼在0x0800與0xffff之間,所以數(shù)據(jù)如果是中文,采用UTF-8編碼數(shù)據(jù)量會增加50%。
Unicode與UTF-8轉(zhuǎn)換的規(guī)則簡述如下:
(1)如果Unicode編碼的16位二進(jìn)制數(shù)的前9位是0,則UTF-8編碼用1個字節(jié)來表示,這個字節(jié)的首位是“0”,剩下的7位與原二進(jìn)制數(shù)據(jù)的后7位相同。例如:
Unicode編碼:"u0061 = 00000000
01100001
UTF-8編碼:01100001 = 0x61
(2)如果Unicode編碼的16位二進(jìn)制數(shù)的頭5位是0,則UTF-8編碼用2個字節(jié)來表示,首字節(jié)以“110”開頭,后面的5位與原二進(jìn)制數(shù)據(jù)除去前5個零后的最高5位相同;第二個字節(jié)以“10”開頭,后面的6位與原二進(jìn)制數(shù)據(jù)中的低6位相同。例如:
Unicode編碼:"u00A9 = 00000000 10101001
UTF-8編碼:11000010 10101001 =
0xC2 0xA9
(3)如果不符合上述兩個規(guī)則,則用三個字節(jié)表示。第一個字節(jié)以“1110”開頭,后四位為原二進(jìn)制數(shù)據(jù)的高四位;第二個字節(jié)以“10”開頭,后六位為原二進(jìn)制數(shù)據(jù)中間的六位;第三個字節(jié)以“10”開頭,后六位為原二進(jìn)制數(shù)據(jù)的低六位。例如:
Unicode編碼:"u4E2D = 01001110
00101101
UTF-8編碼:11100100 10111000
10101101 = 0xE4 0xB8 0xAD
在UTF-8編碼的多字節(jié)串中,第一個字節(jié)開頭“1”的數(shù)目就是整個字符串中字節(jié)的數(shù)目。
17.1.2 對亂碼產(chǎn)生過程的分析
為了讓使用Java語言編寫的程序能在各種語言的平臺下運(yùn)行,Java在其內(nèi)部使用Unicode字符集來表示字符,這樣就存在Unicode字符集和本地字符集進(jìn)行轉(zhuǎn)換的過程。當(dāng)在Java中讀取字符數(shù)據(jù)的時候,需要將本地字符集編碼的數(shù)據(jù)轉(zhuǎn)換為Unicode編碼,而在輸出字符數(shù)據(jù)的時候,則需要將Unicode編碼轉(zhuǎn)換為本地字符集編碼。
例如,在中文系統(tǒng)下,從控制臺讀取一個字符“中”,實(shí)際上讀取的是“中”的GBK編碼0xD6D0,在Java語言中要將GBK編碼轉(zhuǎn)換為Unicode編碼0x4E2D,此時,在內(nèi)存中,字符“中”對應(yīng)的數(shù)值就是0x4E2D,當(dāng)我們向控制臺輸出字符時,Java語言將Unicode編碼再轉(zhuǎn)換為GBK編碼,輸出到控制臺,中文系統(tǒng)再根據(jù)GBK字符集畫出相應(yīng)的字符。
從上述過程來看,讀取和寫入的過程是可逆的,那么理應(yīng)不會出現(xiàn)中文亂碼問題。然而,實(shí)際應(yīng)用的情形,比上述過程要復(fù)雜得多。在Web應(yīng)用中,通常都包括了瀏覽器、Web服務(wù)器、Web應(yīng)用程序和數(shù)據(jù)庫等部分,每一部分都有可能使用不同的字符集,從而導(dǎo)致字符數(shù)據(jù)在各種不同的字符集之間轉(zhuǎn)換時,出現(xiàn)亂碼的問題。
在Java語言中,不同字符集編碼的轉(zhuǎn)換,都是通過Unicode編碼作為中介來完成的。例如,GBK編碼的字符“中”要轉(zhuǎn)換為ISO-8859-1(同ISO8859-1)編碼,其過程如下:
(1)因?yàn)樵?/span>Java中的字符,都是用Unicode來表示的,所以GBK編碼的字符“中”要轉(zhuǎn)換為Unicode表示:0xD6D0->0x4E2D。
(2)將字符“中”的Unicode編碼轉(zhuǎn)換為ISO-8859-1編碼,因?yàn)?/span>Unicode編碼0x4E2D在ISO-8859-1中沒有對應(yīng)的編碼,于是得到0x3f,也就是字符“?”。
下面的代碼演示了這一過程:
//GBK編碼的字符“中”轉(zhuǎn)換為Unicode編碼表示
String str="中";
//將字符“中”的Unicode編碼轉(zhuǎn)換為ISO-8859-1編碼
byte[]
b=str.getBytes("ISO-8859-1");
for(int
i=0;i<b.length;i++)
{
//輸出轉(zhuǎn)換后的二進(jìn)制代碼。
System.out.print(b[i]);
}
當(dāng)從Unicode編碼向某個字符集轉(zhuǎn)換時,如果在該字符集中沒有對應(yīng)的編碼,則得到0x3f(即問號字符?)。這就是為什么有時候我們輸入的是中文,在輸出時卻變成了問號。
從其他字符集向Unicode編碼轉(zhuǎn)換時,如果這個二進(jìn)制數(shù)在該字符集中沒有標(biāo)識任何的字符,則得到的結(jié)果是0xfffd。例如一個GBK的編碼值0x8140,從GB2312向Unicode轉(zhuǎn)換,然而由于0x8140不在GB2312字符集的編碼范圍(0xa1a1-0xfefe),當(dāng)然也就沒有對應(yīng)任何的字符,所以轉(zhuǎn)換后會得到0xfffd。下面的代碼演示了這一過程。
//構(gòu)造一個二進(jìn)制數(shù)據(jù)。
byte[]
buf={(byte)0x81,(byte)0x40,(byte)0xb0,(byte)0xa1};
//將二進(jìn)制數(shù)據(jù)按照GB2312向Unicode編碼轉(zhuǎn)換。
String str=new
String(buf,"GB2312");
for(int
i=0;i<str.length();i++)
{
//取出字符串中的每個Unicode編碼的字符。
char ch=str.charAt(i);
//將該字符對應(yīng)的Unicode編碼以十六進(jìn)制的形式輸出。
System.out.print(Integer.toHexString((int)ch));
System.out.print("--");
//輸出該字符。
System.out.println(ch);
}