編碼字符集與Java -Java World亂碼問(wèn)題根源之所在。
本文介紹了編碼字符集的概念以及Java與編碼字符集之間的關(guān)系,文章的內(nèi)容來(lái)自于本人工作過(guò)程中的經(jīng)驗(yàn)積累以及網(wǎng)絡(luò)中的相關(guān)文章介紹,如果文章中有任何紕漏歡迎讀者指正,讓我們共同討論學(xué)習(xí)J1.????? 字符
字符是抽象的最小文本單位。它沒有固定的形狀(可能是一個(gè)字形),而且沒有值。“A”是一個(gè)字符,“€”(德國(guó)、法國(guó)和許多其他歐洲國(guó)家通用貨幣的標(biāo)志)也是一個(gè)字符。“中”“國(guó)”這是兩個(gè)漢字字符。字符僅僅代表一個(gè)符號(hào),沒有任何實(shí)際值的意義。
2.????? 字符集
字符集是字符的集合。例如,漢字字符是中國(guó)人最先發(fā)明的字符,在中文、日文、韓文和越南文的書寫中使用。這也說(shuō)明了字符和字符集之間的關(guān)系,字符組成字符集。
3.????? 編碼字符集
編碼字符集是一個(gè)字符集(有時(shí)候也被簡(jiǎn)稱位字符集),它為每一個(gè)字符分配一個(gè)唯一數(shù)字。最早的編碼是iso8859-1,和ascii編碼相似。但為了方便表示各種各樣的語(yǔ)言,逐漸出現(xiàn)了很多標(biāo)準(zhǔn)編碼。
iso8859-1:屬于單字節(jié)編碼字符集,最多能表示的字符范圍是0-255,應(yīng)用于英文系列,除了iso8859-1以外還有其他iso8859系列的編碼,這些編碼都是為了滿足歐洲國(guó)家語(yǔ)言字符的需要而設(shè)計(jì)的。
GB2312/GBK/ GB18030:前面提到的iso8859-1最多只能表示256個(gè)字符,這對(duì)于漢字來(lái)說(shuō)實(shí)在是有些抱歉,所以就有了現(xiàn)在要介紹的漢字國(guó)標(biāo)碼,專門用來(lái)表示漢字,是雙字節(jié)編碼字符集,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。其中GBK編碼能夠用來(lái)同時(shí)表示繁體字和簡(jiǎn)體字,而GB2312只能表示簡(jiǎn)體字,GBK是兼容GB2312編碼的。而GB18030-2000則是一個(gè)更復(fù)雜的字符集,采用變長(zhǎng)字節(jié)的編碼方式,能夠支持更多的字符。需要注意的是中國(guó)政府要求所有在中國(guó)出售的軟件必須支持GB18030。
Unicode:這是最統(tǒng)一的編碼字符集,可以用來(lái)表示所有語(yǔ)言的字符,不兼容任何前面提到的編碼字符集。Unicode 標(biāo)準(zhǔn)始終使用十六進(jìn)制數(shù)字,而且在書寫時(shí)在前面加上前綴“U+”,所以“A”的編碼書寫為“U+
從ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語(yǔ)等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴(kuò)展字符集GBK GB18030這個(gè)發(fā)展過(guò)程基本上也反映了字符集標(biāo)準(zhǔn)的發(fā)展過(guò)程,但這么隨著時(shí)間的推移,尤其是互聯(lián)網(wǎng)讓跨語(yǔ)言的信息的交互變得越來(lái)越多的時(shí)候,太多多針對(duì)本地語(yǔ)言的編碼標(biāo)準(zhǔn)的出現(xiàn)導(dǎo)致一個(gè)應(yīng)用程序的國(guó)際化變得成本非常高。尤其是你要編寫一個(gè)同時(shí)包含法文和簡(jiǎn)體中文的文檔,這時(shí)候一般都會(huì)想到要是用一個(gè)通用的字符集能夠顯示所有語(yǔ)言的所有文字就好了,而且這樣做應(yīng)用也能夠比較方便的國(guó)際化,為了達(dá)到這個(gè)目標(biāo),即使應(yīng)用犧牲一些空間和程序效率也是非常值得的。UNICODE就是這樣一個(gè)通用的解決方案。
4.????? Unicode編碼字符集
Unicode 因?yàn)楸仨殞⒅小㈨n、日、英、法、阿拉伯……等許多國(guó)家所使用的文字都納入,目前已經(jīng)包含了六萬(wàn)多個(gè)字符,所以 Unicode 使用了16個(gè)位來(lái)為字符編碼。因?yàn)?/span> Unicode 使用了 16 位編碼,所以每個(gè)字符都用 16 位來(lái)儲(chǔ)存或傳輸是很自然的事,這種儲(chǔ)存或傳輸?shù)母袷?/span>稱為UTF-16(一種Unicode的字符編碼方案,在這里所說(shuō)的UTF-16并不涉及增補(bǔ)字符的表示,本文將會(huì)在稍后介紹)。但是如果你使用到的字符都是西方字符,那么你一定不會(huì)想用 UTF-16 的格式,因?yàn)轶w積比8位的iso8859-1多了一倍,如此一來(lái)就必須考慮程序運(yùn)行時(shí)各種字符在內(nèi)存中所占空間的性能問(wèn)題,這便引入了字符編碼方案的概念:
字符編碼方案是從一個(gè)或多個(gè)編碼字符集到一個(gè)或多個(gè)固定寬度代碼單元序列的映射。
最常用的代碼單元是字節(jié),所以可以簡(jiǎn)單的認(rèn)為字符編碼方案是為了告訴計(jì)算機(jī)如何將編碼字符集(如Unicode)映射到計(jì)算機(jī)可以識(shí)別的數(shù)據(jù)格式中,如字節(jié)。這種編碼方案往往能夠?yàn)樗鶎?duì)應(yīng)的字符集在計(jì)算機(jī)處理時(shí)提供更為優(yōu)化的空間以及性能上的解決方案。Unicode編碼字符集有三種字符編碼方案,下面將逐一介紹:
l???????? UTF-32* 即將每一個(gè)Unicode編碼表示為相同值的32位整數(shù)。很明顯,它是內(nèi)部處理最方便的表達(dá)方式,但是,如果作為一般字符串表達(dá)方式,則要消耗更多的內(nèi)存。顯而易見,對(duì)于英文字母的表示將需要多個(gè)0字節(jié),僅僅因?yàn)槲覀冃枰?/span>4個(gè)字節(jié)32位來(lái)表示一個(gè)Unicode字符。
l???????? UTF-16 使用一個(gè)或兩個(gè)未分配的 16位代碼單元的序列對(duì)Unicode編碼進(jìn)行編碼。值U+0000至U+FFFF 編碼為一個(gè)相同值的16位單元。增補(bǔ)字符*編碼為兩個(gè)代碼單元,第一個(gè)單元來(lái)自于高代理范圍(U+D800 至 U+DBFF),第二個(gè)單元來(lái)自于低代理范圍(U+DC00 至U+DFFF)。這在概念上可能看起來(lái)類似于多字節(jié)編碼,但是其中有一個(gè)重要區(qū)別:值 U+D800 至 U+DFFF 保留用于 UTF-16;沒有這些值分配字符作為代碼點(diǎn)。這意味著,對(duì)于一個(gè)字符串中的每個(gè)單獨(dú)的代碼單元,軟件可以識(shí)別是否該代碼單元表示某個(gè)單單元字符,或者是否該代碼單元是某個(gè)雙單元字符的第一個(gè)或第二單元。這相當(dāng)于某些傳統(tǒng)的多字節(jié)字符編碼來(lái)說(shuō)是一個(gè)顯著的改進(jìn),在傳統(tǒng)的多字節(jié)字符編碼中,字節(jié)值0x41 既可能表示字母“A”,也可能是一個(gè)雙字節(jié)字符的第二個(gè)字節(jié)。
l???????? UTF-8 使用一至四個(gè)字節(jié)的序列對(duì)編碼Unicode進(jìn)行編碼。U+0000至U+
* ?UTF--32 表示Unicode Transformation Form 32-bit form,UTF-16,UTF-8依此類推。
* ?Unicode 最初設(shè)計(jì)是作為一種固定寬度的 16 位字符編碼。在 Java 編程語(yǔ)言中,基本數(shù)據(jù)類型 char 初衷是通過(guò)提供一種簡(jiǎn)單的、能夠包含任何字符的數(shù)據(jù)類型來(lái)充分利用這種設(shè)計(jì)的優(yōu)點(diǎn)。不過(guò),現(xiàn)在看來(lái),16 位編碼的所有65,536個(gè)字符并不能完全表示全世界所有正在使用或曾經(jīng)使用的字符。于是,Unicode 標(biāo)準(zhǔn)已擴(kuò)展到包含多達(dá) 1,112,064個(gè)字符。那些超出原來(lái)的16位限制的字符被稱作增補(bǔ)字符。
5.????? Java與編碼字符集
從上面的介紹我們知道了Unicode編碼字符集可以用來(lái)表示世界上所有的語(yǔ)言文字。Java內(nèi)部處理字符使用的字序方式是Unicode,這是一種通行全球的編碼方式,他使Java語(yǔ)言能夠描述世界上所有的文字。在Java程序中對(duì)各種字符在內(nèi)存中處理是使用Unicode的UTF-8編碼方式,這也是因?yàn)?/span>UTF-8的特點(diǎn)所決定的,Class File(也就是bytecode)中有一欄位叫做常數(shù)區(qū)(Constant Pool),一律使用UTF-8為子元編碼。
這看起來(lái)一切正常,Java可以處理世界上所有的字符,一切都是按照秩序在運(yùn)行,但是,從前面的討論我們知道,世界上并不是僅僅只有Unicode編碼字符集,同時(shí)存在的還有iso8859-1、GBK等編碼字符集,就是在Unicode中也同樣存在著UTF-8,UTF-16,UTF-32等多種編碼,如果傳入的字節(jié)編碼采用的是GB18030,而采用的解碼方式為UTF-8那會(huì)有什么后果呢,看看下面的代碼片段:
public static final String TEST_RESOURCE = "你好";
??? public static void testEncoding() {
??????? try {
??????????? byte[] bytes = TEST_RESOURCE.getBytes("GB18030");
??????????? String result = new String(bytes, "UTF-8");
??????????? System.out.println("Receive value: [" + result + "].");
??????? } catch (UnsupportedEncodingException e) {
??????????? // TODO Auto-generated catch block
??????????? e.printStackTrace();
??????? }
??? }
執(zhí)行以上的代碼片段,在我的機(jī)器(Win XP中文版)上面得到的結(jié)果是:
?????? Receive value: [???].
明白了吧,這就是久負(fù)盛名的亂碼問(wèn)題的根源,目前在市面上存在有多種編碼字符集,以及編碼字符集的編碼方案,所以雖然在Java中內(nèi)部是以Unicode的UTF-8來(lái)處理各種字符的表示以及運(yùn)算,但是這僅僅是在Java內(nèi)部而以,如果Java程序需要和外部應(yīng)用系統(tǒng)進(jìn)行交互,比如與操作系統(tǒng),數(shù)據(jù)庫(kù)系統(tǒng)之間的交互,那么在這些交互過(guò)程中如何處理字符集的編碼解碼是解決好Java應(yīng)用程序亂碼問(wèn)題的根源。
如果將上面的代碼塊修改成如下的代碼塊:
??? public static void testEncoding() {
??????? try {
??????????? byte[] bytes = TEST_RESOURCE.getBytes("GB18030");
??????????? String result = new String(bytes, "GB18030");
??????????? System.out.println("Receive value: [" + result + "].");
??????? } catch (UnsupportedEncodingException e) {
??????????? // TODO Auto-generated catch block
??????????? e.printStackTrace();
??????? }
??? }
???????????????????? 注意紅色標(biāo)注的地方,執(zhí)行以上的代碼塊將會(huì)受到預(yù)期的結(jié)果:
??????????????????????????? Receive value: [你好].
統(tǒng)一字符的編碼類型和解碼類型,如此一來(lái)任何亂碼問(wèn)題都不會(huì)再是問(wèn)題了。在網(wǎng)上可以搜索到N多的關(guān)于如何解決J2EE亂碼問(wèn)題的文章,我在這里也就不廢話了,我只是想說(shuō)說(shuō)Java亂碼問(wèn)題的根源之所在。
如果你仔細(xì)想想Java的開發(fā)過(guò)程,原文件編寫、javac編譯、java執(zhí)行,這每一步驟都會(huì)涉及到編碼的轉(zhuǎn)換過(guò)程,這個(gè)過(guò)程總是存在的,只是有的時(shí)候用默認(rèn)的參數(shù)進(jìn)行。
我們從javac這個(gè)命令來(lái)開始我們的分析,編譯的時(shí)候,如果你不說(shuō)明源文件編碼方式的話,javac 編譯器在讀進(jìn)此原始程序文件開始編譯之前,會(huì)先去詢問(wèn)操作系統(tǒng)檔案預(yù)設(shè)的編碼方式為何。以我的操作系統(tǒng)WIN XP 中文版來(lái)說(shuō),javac 會(huì)先詢問(wèn)WIN XP,得知當(dāng)前的編碼是用GB18030的方式編碼。然后就可以將源文件由GB18030轉(zhuǎn)成Unicode編碼方式,開始進(jìn)行編譯。在這里就會(huì)發(fā)生一下一些編碼問(wèn)題:
l???????? 如果操作系統(tǒng)的國(guó)籍資料設(shè)定錯(cuò)誤,會(huì)造成javac編譯器取得的編碼信息是錯(cuò)誤的,這里也有可能由于系統(tǒng)屬性file.encoding設(shè)置錯(cuò)誤,在我的系統(tǒng)中該屬性為GB18030,可以通過(guò)代碼System.out.println(System.getProperties());輸出可能的系統(tǒng)屬性。
l???????? 較差勁的編譯器可能沒有主動(dòng)詢問(wèn)操作系統(tǒng)的編碼方式,而是采用編譯器預(yù)設(shè)的編碼方式,當(dāng)然這種情況對(duì)于目前先進(jìn)的編譯器來(lái)說(shuō)已經(jīng)不存在了,但是這確實(shí)是一種可能的原因。
l???????? 源代碼是在英文操作系統(tǒng)上書寫采用編碼iso8859-1,寫好以后再將源代碼傳遞給中文操作系統(tǒng)進(jìn)行編譯,這樣由于兩個(gè)操作系統(tǒng)的編碼方式不同,也會(huì)造成javac執(zhí)行錯(cuò)誤。
明白了吧,這些問(wèn)題在我們?nèi)粘5拇a編寫過(guò)程中,往往由于默認(rèn)的屬性都正好能滿足我們的需要,即源代碼的書寫以及編譯都采用操作系統(tǒng)默認(rèn)的編碼方式,所以可能很多人到目前為止都沒有遇見過(guò)諸如此類的問(wèn)題,但是我們要知道,這些問(wèn)題確實(shí)是存在的。
Java編譯器在執(zhí)行過(guò)程中給我們提供了可選的encoding參數(shù)來(lái)告訴編譯器該采用何種編碼方式將讀入的源文件轉(zhuǎn)換成Unicode編碼方式,然后再進(jìn)行后續(xù)的編譯工作。
javac –encoding GB18030 ….
6.????? Inside
OK,通過(guò)前面的介紹希望讀者能夠?qū)?/span>Java以及各種字符編碼之間的關(guān)系有個(gè)簡(jiǎn)單的了解,下面我在繼續(xù)總結(jié)一下:
l???????? Javac是以系統(tǒng)默認(rèn)編碼(file.encoding系統(tǒng)屬性)讀入源文件,然后按Unicode進(jìn)行編碼的。
l???????? 在JAVA運(yùn)行的時(shí)候,JAVA也是采用Unicode編碼的,為了高度利用內(nèi)存空間提高效率對(duì)Unicode字符編碼采用了UTF-8的方式編碼,并且默認(rèn)輸入和輸出的都是操作系統(tǒng)的默認(rèn)編碼。
l???????? 也就是說(shuō)在new String(bytes,encode)中,系統(tǒng)認(rèn)為輸入的是編碼為encode的字節(jié)流,換句話說(shuō),如果按encode來(lái)翻譯bytes才能得到正確的結(jié)果;而在new String(bytes)中采用的就是根據(jù)file.encoding系統(tǒng)屬性讀入的編碼方式來(lái)進(jìn)行編碼,同樣也必須根據(jù)系統(tǒng)默認(rèn)的編碼才能得到正確的結(jié)果,這個(gè)結(jié)果最后要在JAVA中保存,它還是要從這個(gè)encode轉(zhuǎn)換成Unicode,因?yàn)樵?/span>JAVA中各種字符均是以Unicode的形式來(lái)處理的。
l???????? 也就是說(shuō)有bytes-->encode字符-->Unicode字符的轉(zhuǎn)換;而在String.getBytes([encode])中,系統(tǒng)要做一個(gè)Unicode字符-->encode字符-->bytes的轉(zhuǎn)換。
希望通過(guò)本文的介紹能夠使你對(duì)字符集編碼的概念以及Java與字符集編碼之間的關(guān)系有個(gè)清楚的認(rèn)識(shí),我相信,如果搞清楚了他們之間的關(guān)系,那個(gè)在Java world鼎鼎有名的亂碼問(wèn)題將一去不再?gòu)?fù)返了J
posted on 2006-09-24 00:08 Find it, try it, experience it 閱讀(9361) 評(píng)論(5) 編輯 收藏