Java 字節流與字符流(3) 轉

在上一篇中比較了使用字節流和字符流來讀取(寫入)文本文件的優劣後,這一篇主要探討缺省編碼這個主題。

字符流使用缺省編碼

通過前面的例子,已經得出了一個結論:字符流=字節流+編碼。

可以在構建字符流時顯示傳入編碼參數,那麼所得到的字符流就會以該編碼來編碼(encode)解碼(decode)字節流,這會給文本數據處理帶來極大方便。

但有時,構建字符流時也可以不傳入編碼參數,比如如下直接構建一個 InputStreamReader :

對比註釋掉的代碼,可以看出此時只用了字節流,沒有指定編碼。

那麼這時它到底是以什麼編碼來解碼(decode)它要讀取的字節流呢?比如上面的 utf-8 編碼的文本文件它能否正常讀取呢?我們想知道,編碼參數是必要的嗎?如果沒有指定,它是否會自動猜測出正確的編碼呢?

這裏先給出結論,那就是編碼參數是必要的,是 decode(或 encode)所必不可少的,之所以可以省略那是因爲系統會爲我們提供缺省值。

然後,它也不會去自動猜測。(至少這裏的 BufferedReader 之類的不會去猜測,如果你使用其它的第三方增強的工具類,那就未可知)沒有指定它就用缺省去 decode(或 encode)。

那麼,這就可能導致一個問題。比如現在要讀取的文件是 utf-8 編碼的,而假如系統的缺省編碼值是 gbk 的話,顯然,它將不能正確地解碼!

缺省編碼來自哪裏?

但是,系統的缺省編碼到底是什麼呢?這個缺省值可以通過這樣來得到:

也就是用 Charset.defaultCharset() 或 System.getProperty("file.encoding") 的方式可以得到它。

當我在本地的 Windows 系統執行這段程序時,結果如下:

缺省編碼是 gbk。

而當我把這個 class 類上傳到雲主機時,那是個 linux 系統(具體爲 centos 7),再執行時發現結果又不同了:

此時的結果是 ASCII(ANSI_X3.4-1968 是它的一個別名)。不同系統,甚至不同版本這個值可能有很大差異,跟具體的環境配置也有關,在你的 linux 系統裏可能輸出是 utf-8 等值。

如果你用的是 Mac,你也可以試一試看看結果是什麼。(我沒有 Mac,所以沒有測試這個~)

不過可以通過增加運行參數調整這個值,比如以這樣的方式

java -Dfile.encoding=utf-8 DefaultEncoding

來運行,缺省編碼就變成 utf-8 了:

在 linux 系統上也是同樣的:

而我在 Windows 系統下的 Eclipse 工程裏用它的“運行”來做這個測試時,結果是 UTF-8 而不是 GBK,因爲工程我設置了缺省編碼是 UTF-8,而 Eclipse 運行時會根據你工程設置傳入這個參數,具體如下,在 Debug 視圖中,選中運行的實例–右鍵–選擇“properties”,在彈出的窗口中的 Command Line(命令行)部分可以看到指定了編碼:

如果你工程指定的編碼是 GBK 或沒有指定,在 Windows 下輸出結果就會跟在 CMD 命令行窗口中那樣,結果應該爲 GBK。

在 Idea IDE 中也是類似的,比如工程編碼是 GBK,那麼運行出來的結果也是 GBK:

在上圖中,勾選“use soft wraps”(使用軟換行),

這裏因爲是這個生成的命令行特別長,主要是那一堆的 classpath,跟 eclipse 類似,所以讓其換行顯示。

然後單擊所運行的命令展開它的細節,可以發現也傳入了相關 file.encoding 參數:

這個參數決定了缺省值是什麼。如果你沒有指定,JVM 將詢問它所在的運行環境(一般也就是操作系統了)來得到一個缺省值。

如果你運行 web server 的程序,比如 tomcat,也可以照此查看它的參數,或通過它改變缺省值。通過顯式指定一個缺省值,可以在各種運行環境下保持一致。

比如這是我本機 Eclipse 上運行 tomcat 插件時的命令行:

可以看到也是傳入了這個參數。不過有一點要注意的是,至少就 server 中的這個缺省值而言,它影響的是上述所說的 new InputStreamReader 以及 getBytes 之類的方法。而在 servlet 的響應流中,如果使用 response.getWriter 得到的 PrintWriter 這個字符流,它的缺省又是另外一個值,具體爲 iso-8859-1.

這個缺省值來自於 servlet 的規範,進一步的原因則是來源於 http 規範。關於這一話題將在另一篇文章中再去分析。

所以,總體而言,如果你構建字符流時使用缺省編碼,那麼情況會比較混亂,也很容易出錯。

使用缺省編碼去讀取

現在來看一些具體的例子。假如要讀取的還是之前那個 utf-8 編碼的文本文件,內容還是“hi你好”,總計 8 個字節,具體十六進制如下:

寫一段讀取它的程序如下:

然後在 CMD 中編譯並運行,那麼結果是這樣的:

可以看到出來的並不是想要的“hi你好”,而是很奇怪的“hi浣犲ソ ”。那麼其中的原因也不難理解,因爲沒有指定編碼,所以程序就用缺省編碼去解碼這段字節流,此時是 gbk,所以就把那六字節按每兩字節去解析,結果得到三個漢字:

對於我們中國人來說,會意識到出了問題,因爲這三字很不常見,但對於歪果仁來說,他可能沒有什麼感覺,反正就是一些方塊字而已,反正他都不認識。

假如現在顯式指定 –Dfile.encoding=utf-8,那麼結果就 OK 了:

又假如指定爲 –Dfile.encoding=iso-8859-1 呢?那麼結果就按 iso-8859-1 解析了:

這裏它顯示爲六個問號,其實正常來說,應該是有對應字符的。如果在 eclipse 本身執行,console 中的輸出是這樣的:

所以顯示爲問號可能是 CMD 程序本身的一些問題。

畢竟,前面也一再提到,文本文件內容本身是不包含有編碼信息的(BOM 的情況除外),就這麼 8 個字節,你有很多的解析可能,按 gbk,按 utf-8,按 iso-8859-1,都能解析出一些字符來。

總結

由此也不難明白,缺省絕對不是什麼好主意。假如你不知道別人啓動 web server 時傳入了什麼參數,或者沒傳參數時你不知道程序到底運行在什麼操作系統上,你就完全無法預料你的程序會運行出什麼結果。

通常,我們把這種情況稱爲對環境形成了依賴,是種不穩定因素。程序員在開發活動中常遇到的一種情況是:“在我這裏運行明明是正常的,怎麼放到其它地方去就不正常了呢?”就有可能是這種環境差異導致的。

假如你想把這段字節流穩定地解析成“hi你好”,那麼最好的方式就是在你的代碼裏顯式構建 utf-8 的字符流,而不是依賴缺省。

當然,有人可能會說,每次都要顯式指定,我覺得太麻煩了,我還是希望用缺省,那麼這種情況你就要保證所有環節都使用統一的編碼,比如所有你要讀入的文件都是用這一編碼編碼的等等,然後用 –Dfile.encoding 參數指定這一編碼。假如你無法保證在所有環節統一,恐怕你就不能如此依賴缺省了。

關於字符流使用缺省編碼的話題就討論到這裏。在最後一篇,將討論最後的一個話題:到底怎樣纔算是一個‘字符’?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章