用Java從UTF-8文本文件中讀取內容

整體思路:

(1)讀取源文件內容(UTF-8編碼無BOM), 通過FileReader字符流完成;

(2)向目標文件寫內容,通過FileWriter字符流完成;

(3)向控制檯輸出內容;

最後的結果是,目標文件爲UTF-8編碼格式,一切正常;控制檯得到的輸出,中文字符爲亂碼,英文字母,標點符號一切正常。

解碼過程

我查閱了Java API文檔,官方在線文檔在這裏。結合之前理解的Java輸入流與輸出流的知識,找到了使用另一種方法,在不更改控制檯編碼集的情況下,得到正常輸出。

Java的FileReader字符輸入流,是Java中讀取文本文件的重要類,爲了理解文本文件讀取具體過程,查閱API doc,發現FileReader類有如下繼承關係

java.io.Reader
    java.io.InputStreamReader
        java.io.FileReader

InputStreamReader是一個“轉換流”,將字節輸入流轉換成字符輸入流,FileReader繼承了此類。說明FileReader類在讀取文本文件時,首先將文件作爲字節流讀入,然後使用InputStreamReader的類似功能,將字節流轉換爲字符流。

這裏需要解釋下,所有內容在計算機中的存儲形式,都是二進制形式,原始的二進制文件的基本讀取單位是字節(byte),而對於文本文件,字符使用一個整數來表示的,unicode中,把這個表示字符的整數稱爲“code point”。encode(編碼)和decode(解碼)的過程,可用下面的方式理解:

字符.encode()------>二進制字節

二進制字節.decode()--------->字符

InputStreamReader轉換流所實現的功能,就是一個decode的過程。

API doc 中,FileReader類的描述中,有這樣一句:

“The constructors of this class assume that the default character encoding and the default byte-buffer size are appropriate”

意即“該類構造器假定默認字符編碼和默認緩衝字節大小是合適的”。說明FileReader在將字節與字符進行encode(編碼)和decode(解碼)的過程中,是假定了一個默認的,“合適”的字符集。

而FileReader的父類InputStreamReader,是這樣描述的

“It reads bytes and decodes them into characters using a specified charset. The charset that it uses may be specified by name or may be given explicitly, or the platform's default charset may be accepted.”

意即“InputStreamReader讀取字節,並將它們解碼爲字符,解碼過程中使用指定的字符集(charset)……如果沒有顯示指定字符接,使用平臺的默認字符集。”Windows平臺的默認字符集是GBK(ANSI標準),Linux平臺的默認字符集是“UTF-8”。而如果一個UTF-8字節編碼的文本文件,使用GBK字符集解碼,那當然就會產生亂碼,反過來也一樣。而英文字母、符號永遠不會出現亂碼的原因,是ASCII字符表是UTF-8的一個子集,也是GBK的一個子集,UTF-8中英文字母的字節碼與GBK字符集中英文字母的字節碼完全相同。

因此,另外一種解決方式,是使用InputStreamReader轉換流,將一個字節流轉換爲字符流,並在轉換過程中,指定使用“UTF-8”字符集,對字節進行解碼。核心代碼如下:

InputStreamReader ipr = new InputStreamReader(new FileInputStream("HelloTest.java"),Charset.forName("UTF-8");
//使用BufferedReader進行封裝,是爲了調用readLine()函數,方便操作
BufferedReader br = new BufferedReader(ipr);
//其餘操作......

等等,那編碼過程呢?

在指定解碼字符集之後,就能得到正常輸出。但是有另外一個問題,第一種解決辦法,是更改了MyEcplise控制檯的默認編碼,纔得到正常輸出。同時由於Windows的cmd窗口無法更改默認編碼,只能使用GBK,依然是亂碼。爲什麼在Java代碼中,更改了InputStreamReader的一個參數,在GBK編碼的控制檯,依然能正常輸出“UTF-8”字符呢?

Java的標準輸出流System.out,將字符內容輸出到控制檯。查閱Java Doc文檔,可以看到,out其實是System類中的一個靜態成員。而out的類型爲PrintStream。因此,關鍵是要理解PrintStream類中的方法成員println()/print()的執行原理。在Java Doc文檔中,有描述“All characters printed by a PrintStream are converted into bytes using the platform’s default character encoding. ”意即“被PrintStream打印(print)的所有字符,都使用平臺默認的字符編碼集,轉換爲字節。”。也就是說,System.out.print()/pinrtln()方法,使用平臺默認字符集,將字符串encode(編碼)爲字節,然後傳給了控制檯,控制檯接收到字節後,當然會使用平臺默認的字符集,將字節decode(解碼)爲字符串。

即Java會將程序中的字符,先編碼,再放入標準輸出流System.out,編碼字符集爲平臺默認字符集。因此,只要在讀取輸入流的過程中,將字節正確地解碼爲字符,後續的標準輸出其實是與原文件編碼格式無關的。

但是之前說過,計算機中所有的數據都是二進制字節,文本文件的存儲方式也是二進制字節;那麼,在Java程序中,字符是以什麼形式保存的?再次查閱Java Doc,發現Java中char數據類型用如前所述的 UNICODE 表示,UNICODE標準給每個字符分配一個唯一的整數,使用16進制編碼方式表示,一共可以表示65536個字符,可以理解爲無符號的16位整數。因此Java程序中,char類型是可以和Int類型相互轉換的。

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