天天看點

【java】Java編碼字元集與字元集編碼入門(五) Java代碼中的字元編碼轉換Part1...

  • Java使用的統一字元集Unicode

    如果你是JVM的設計者,讓你來決定JVM中所有字元的表示形式,你會不會允許使用各種編碼方式的字元并存? 

    我想你的答案是不會,如果在記憶體中的Java字元可以以GB2312,UTF-16,BIG5等各種編碼形式存在,那麼對開發者來說,連進行最基本的字元串列印、連接配接等操作都會寸步難行。例如一個GB2312的字元串後面連接配接一個UTF-8的字元串,那麼連接配接後的最終結果應該是什麼編碼的呢?你選哪一個都沒有道理。

    是以牢記下面這句話,這也是Java開發者的共同意志:在Java中,字元隻以一種形式存在,那就是Unicode(注意到我們沒有選擇特定的編碼,直接使用它們在字元集中的編号,這是統一的唯一方法)。

    但“在Java中”到底是指在哪裡呢?就是指在JVM中,在記憶體中,在你的代碼裡聲明的每一個char,String類型的變量中。例如你在程式中這樣寫 

    char han='漢'; 

    在記憶體的相應區域,這個字元就表示為0x6C49.可以用下面的代碼證明一下: 

    char han='漢';

    System.out.format("%x",(short)han); 

    輸出是:6c49

    反過來用Unicode編号來指定一個字元也可以,像這樣: 

    char han=0x6c49;

    System.out.println(han);

    輸出是:漢 

    這其實也是說,隻要你正确的讀入了“漢”這個字,那麼它在記憶體中的表示形式一定是0x6C49,沒有任何其他的值能代表這個字(當然,如果你讀錯了,那結果是什麼就不知道了,範偉說:讀,讀錯了呀,那還等于好幾億呢;本山大哥說:好幾億你也沒答上,請聽下一題)。

    JVM的這種約定使得一個字元存在的世界分為了兩部分:JVM内部和OS的檔案系統。在JVM内部,統一使用Unicode表示,當這個字元被從JVM内部移到外部(即儲存為檔案系統中的一個檔案的内容時),就進行了編碼轉換,使用了具體的編碼方案(也有一種很特殊的情況,使得在JVM内部也需要轉換,不過這個是後話)。 

    是以可以說,所有的編碼轉換就隻發生在邊界的地方,JVM和OS的交界處,也就是你的各種輸入輸出流(或者Reader,Writer類)起作用的地方。

  • Java的IO系統

    話頭扯到這裡就必須接着說Java的IO系統。

    盡管看上去混亂繁雜,但是所有的IO基本上可以分為兩大陣營:面向字元的Reader啊Wrtier啊,以及面向位元組的輸入輸出流。

    下面我來逐一分解,其實一點也不難。

    面向字元和面向位元組中的所謂“面向”什麼,是指這些類在處理輸入輸出的時候,在哪個意義上保持一緻。如果是面向位元組,那麼這類工作要保證系統中的檔案二進制内容和讀入JVM内部的二進制内容要一緻。不能變換任何0和1的順序(也就是檔案是怎麼存的就怎麼取,與檔案儲存的編碼一緻)。是以這是一種非常“忠實于原著”的做法(偶然間讓我想起郭敬明抄襲莊羽的文章,那家夥,太忠實于原著了,笑)。

    這種輸入輸出方式很适合讀入視訊檔案或者音頻檔案,或者任何不需要做變換的檔案内容。

    而面向字元的IO是指希望系統中的檔案的字元和讀入記憶體的“字元”(注意和位元組的差別)要一緻。例如我們的中文版WindowsXP系統上有一個GBK的文本檔案,其中有一個“漢”字,這個字的GBK編碼是0xBABA(而Unicode編号是0x6C49),當我們使用面向字元的IO把它讀入記憶體并儲存在一個char型變量中時,我希望IO系統不要傻傻的直接把0xBABA放到這個char型變量中,我甚至都不關心這個char型變量具體的二進制内容到底是多少,我隻希望這個字元讀進來之後仍然是“漢”這個字。 

    從這個意義上也可以看出,面向字元的IO類,也就是Reader和Writer類,實際上隐式的為我們做了編碼轉換,在輸出時,将記憶體中的Unicode字元使用系統預設的編碼方式進行了編碼,而在輸入時,将檔案系統中已經編碼過的字元使用預設編碼方案進行了還原。我兩次提到“預設”,是說Reader和Writer的聰明也僅此而已了,它們隻會使用這個預設的編碼來做轉換,你不能為一個Reader或者Writer指定轉換時使用的編碼。這也意味着,如果你使用中文版WindowsXP系統,而上面存放了一個UTF-8編碼的檔案,當你使用Reader類來讀入的時候,它會傻傻的使用GBK來做轉換,轉換後的内容當然驢唇不對馬嘴! 

    這種笨,有時候其實是一種傻瓜式的功能提供方式,對大多數初級使用者(以及不需要跨平台的進階使用者)來說反而是件好事。 

    但我們不一樣啦,我們都是國家棟梁,肩負着趕英超美的責任,必須師夷長技以治夷,是以我們總還要和GBK編碼以外的檔案打交道。

Java中的編碼轉換(俗稱轉碼)

    說了上面這些内容,想必聰明的讀者已經看出來,所謂編碼轉換就是一個字元與位元組之間的轉換,是以Java的IO系統中能夠指定轉換編碼的地方,也就在字元與位元組轉換的地方,那就是(讀者:InputStreamReader和OutputStreamWriter!作者:太強了,都會搶答了!)

    PrintStream也可以對OutputStream進行包裝并指定編碼方式:PrintStream(OutputStream out,

boolean autoFlush, String encoding),但實質上也是調用OutputStreamWriter來實作的。

    這兩個類是位元組流和字元流之間的擴充卡類,是以他們肩負着編碼轉換的任務簡直太自然啦!要注意,實際上也隻能在這兩類執行個體化的時候指定編碼,是不是很好記呢?

    下面來寫一段小程式,來把“漢”字用我們非常崇拜的UTF-8編碼寫到檔案中! 

try {
	PrintWriter out = new PrintWriter(new OutputStreamWriter(
			new FileOutputStream("c:/utf-8.txt"), "UTF-8"));
	try {
	    out.write("漢");
	} finally {
	    out.close();
	}
} catch (IOException e) {
	throw new RuntimeException(e);

}
           

    運作之後到c盤下去找utf-8.txt這個檔案,用UltraEdit打開,使用16進制檢視,看到了什麼?它的值是0xE6B189!噢耶!(讀者:這,這有什麼好高興的……)