2011年5月25日 星期三

Java的編碼問題匯總


問題一:在JAVA中讀取文件時應該採用什麼編碼?

Java的讀取文件的方式總體可以分為兩類:按字節讀取和按字符讀取。按字節讀取就是採用 InputStream.read()執行方法來讀取字節,然後保存到一個字節 []數組中,最後經常用新的字符串(字節 []);把字節數組轉換成字符串。在最後一步隱藏了一個編碼的細節,新的字符串(字節 []);會使用操作系統默認的字符集來解碼字節數組,中文操作系統就是GBK的。而我們從輸入流裡讀取的字節很可能就不是GBK的編碼的,因為從輸入流裡讀取的字節編碼取決於被讀取的文件自身的編碼。舉個例子:我們在ð:盤新建一個名為 demo.txt的文件,寫入“我們。”並保存。此時 demo.txt編碼是ANSI和中文操作系統下就是GBK的。此時我們用輸入字節流讀取該文件所得到的字節就是使用GBK的方式編碼的字節。那麼我們最終新的字符串(字節 []);時採用平台默認的GBK的來編碼成字符串也是沒有問題的(字節編碼和默認解碼一致)。試想一下,如果在保存demo.txt文件時,我們選擇的UTF - 8編碼,那麼該文件的編碼就不在是的ANSI了,而變成了UTF - 8的。仍然採用輸入字節流來讀取,那麼此時讀取的字節和上一次就不一樣了,這次的字節是使用UTF - 8編碼的字節。兩次的字節顯然不一樣,一個很明顯的區別就是:GBK的每個漢字兩個字節,而UTF - 8的每個漢字三個字節。如何我們最後還使用新字符串(字節 []);來構造字符串對象,則會出現亂碼,原因很簡單,因為構造時採用的默認解碼 GBK的,而我們的字節是使用UTF - 8字節。正確的辦法就是使用新的字符串(字節 []“的UTF - 8”);來構造的String對象。此時我們的字節編碼和構造使用的解碼是一致的,不會出現亂碼問題了。



說完字節輸入流,再來說說字節輸出流。

我們知道如果採用字節輸出流把字節輸出到某個文件,我們是無法指定生成文件的編碼的(假設文件以前不存在),那麼生成的文件是什麼編碼的呢?經過測試發現,其實這取決於寫入的字節編碼格式。比如以下代碼:

OutputStream的輸出 =新FileOutputStream(為“D:\ \ demo.txt”);

out.write(“我們”。getBytes()構造);

getBytes()構造會採用操作系統默認的字符集來編碼字節,這裡就是GBK的,所以我們寫入文件的是GBK的demo.txt編碼的字節。那麼這個文件的編碼就是GBK的。如果稍微修改一下程序: out.write(“我們”。的GetBytes(“使用UTF - 8”));此時我們寫入的字節就是使用UTF - 8的,那麼 demo.txt文件編碼就是使用UTF - 8。這裡還有一點,如果把“我們”換成 123或農業銀行之類的的ASCII碼字符,那麼無論是採用的GetBytes()或者的GetBytes(“使用UTF - 8”),那麼生成的文件都將是GBK的編碼的。

這裡可以總結一下,輸入流中的字節編碼取決文件本身的編碼,而OutputStream的生成文件的編碼取決於字節的編碼。



下面說說採用字符輸入流來讀取文件。

首先,我們需要理解一下字符流。其實字符流可以看做是一種包裝流,它的底層還是採用字節流來讀取字節,然後它使用指定的編碼方式將讀取字節解碼為字符。說起字符流,不得不提的就是的InputStreamReader。以下是Java API的對它的說明:的InputStreamReader是字節流通向字符流的橋樑:它使用指定的字符集讀取字節並將其解碼為字符。它使用的字符集可以由名稱指定或顯式給定,否則可能接受平台默認的字符集。說到這裡其實很明白了,的InputStreamReader在底層還是採用字節流來讀取字節,讀取字節後它需要一個編碼格式來解碼讀取的字節,如果我們在構造的InputStreamReader沒有傳入編碼方式,那麼會採用操作系統默認的GBK的來解碼讀取的字節。demo.txt還用上面的例子,假設編碼方式為 GBK的demo.txt,我們使用如下代碼來讀取文件:

在新的InputStreamReader的InputStreamReader =(新FileInputStream的(“demo.txt”));

那麼我們讀取不會產生亂碼,因為文件採用 GBK的編碼,所以讀出的字節也是GBK的編碼的,而的InputStreamReader默認採用解碼也是GBK的。如果把demo.txt編碼方式換成 UTF - 8的,那麼我們採用這種方式讀取就會產生亂碼。這是因為字節編碼(使用UTF - 8)和我們的解碼編碼(GBK的)造成的。解決辦法如下:

在新的InputStreamReader的InputStreamReader =(新FileInputStream的(“demo.txt”),“使用UTF - 8”);

給的InputStreamReader指定解碼編碼,這樣二者統一就不會出現亂碼了。



下面說說字符輸出流。

字符輸出流的原理和字符輸入流的原理一樣,也可以看做是包裝流,其底層還是採用字節輸出流來寫文件。只是字符輸出流根據指定的編碼將字符轉換為字節的。字符輸出流的主要類是:OutputStreamWriter。Java API的解釋如下:OutputStreamWriter是字符流通向字節流的橋樑:使用指定的字符集將要向其寫入的字符編碼為字節。它使用的字符集可以由名稱指定或顯式給定,否則可能接受平台默認的字符集。說的很明白了,它需要一個編碼將寫入的字符轉換為字節,如果沒有指定則採用 GBK的編碼,那麼輸出的字節都將是GBK的編碼,生成的文件也是GBK的編碼的。如果採用以下方式構造 OutputStreamWriter:

OutputStreamWriter輸出 =新OutputStreamWriter(新FileOutputStream(“dd.txt”),“使用UTF - 8”);

那麼寫入的字符將被編碼為 UTF - 8編碼的字節,生成的文件也將是使用UTF - 8格式的。



問題二:既然讀文件要使用和文件編碼一致的編碼,那麼 javac的編譯文件也需要讀取文件,它使用什麼編碼呢?

這個問題從來就沒想過,也從沒當做是什麼問題。正是因為問題一而引發的思考,其實這裡還是有東西可以挖掘的。下面分三種情況來探討,這三種情況也是我們常用的編譯的Java源文件的方法。

1.javac在控制台編譯的Java類文件。

通常我們手動建立一個 java的文件Demo.java,並保存。此時 Demo.java文件的編碼為 ANSI和中文操作系統下就是GBK的。然後使用javac的命令來編譯該源文件。“javac的Demo.java”。javac的也需要讀取的Java文件,那麼 javac的是使用什麼編碼來解碼我們讀取的字節呢?其實 javac的採用了操作系統默認的GBK的編碼解碼我們讀取的字節,這個編碼正好也是Demo.java文件的編碼,二者一致,所以不會出現亂碼情況。讓我們來做點手腳,在保存Demo.java文件時,我們選擇的UTF - 8保存。此時 Demo.java文件編碼就是UTF - 8的了。我們再使用“javac的Demo.java”來編譯,如果Demo.java裡含有中文字符,此時控制台會出現警告信息,也出現了亂碼。究其原因,就是因為採用了GBK的javac的編碼解碼我們讀取的字節。因為我們的字節是使用UTF - 8編碼的,所以會出現亂碼。如果不信的話你可以自己試試。那麼解決辦法呢?解決辦法就是使用的javac的編碼參數來制定我們的解碼編碼。如下: javac的編碼的UTF - 8 Demo.java。這裡我們指定了使用的UTF - 8來解碼讀取的字節,由於這個編碼和Demo.java文件編碼一致,所以不會出現亂碼情況了。



2.Eclipse中編譯的Java文件。

我習慣把Eclipse中的編碼設置成UTF - 8的。那麼每個項目中的Java的源文件的編碼就是使用UTF - 8。這樣編譯也從沒有問題,也沒有出現過亂碼。正是因為這樣才掩蓋了使用javac的可能出現的亂碼。那麼 Eclipse的是如何正確編譯文件編碼為 UTF - 8編碼的Java的源文件的呢?唯一的解釋就是Eclipse的自動識別了我們的Java源文件的文件編碼,然後採取了正確的編碼參數來編譯我們的Java的源文件。功勞都歸功於 IDE中的強大了。



3。使用的Ant來編譯的Java文件。

螞蟻也是我常用的編譯的Java文件的工具。首先,必須知道螞蟻在後台其實也是採用的javac來編譯的Java源文件的,那麼可想而知,一會出現的問題在螞蟻中也會存在。如果我們使用螞蟻來編譯的UTF - 8編碼的Java的源文件,並且不指定如何編碼,那麼也會出現亂碼的情況。所以螞蟻的編譯命令有一個屬性“編碼”允許我們指定編碼,如果我們要編譯源文件編碼為 UTF - 8編碼的java的文件,那麼我們的命令應該如下:

“>

指定了編碼也就相當於“javac的編碼”了,所以不會出現亂碼了。



問題三:tomcat的中編譯的JSP的情況。

這個話題也是由問題二引出的。既然javac的編譯的Java源文件需要採用正確的編碼,那麼 Tomcat的編譯的JSP時也要讀取文件,此時的Tomcat採用什麼編碼來讀取文件?會出現亂碼情況嗎?下面我們來分析。

我們通常會在jsp的開頭寫上如下代碼:

<%@網頁的語言 =“爪哇”的contentType =“text / html的,字符集= UTF - 8”pageEncoding =“的UTF - 8”%>

我常常不寫 pageEncoding這個屬於,也不明白它的作用,但是不寫也沒出現過亂碼情況。其實這個屬性就是告訴 tomcat的採用什麼編碼來讀取 JSP的文件的。它應該和jsp的文件本身的編碼一致。比如我們新建個 jsp的文件,設置文件編碼為 GBK的,那麼此時我們的pageEncoding應該設置為 GBK的,這樣我們寫入文件的字符就是GBK的編碼的,Tomcat的讀取文件時採用也是GBK的編碼,所以能保證正確的解碼讀取的字節。不會出現亂碼。如果把pageEncoding設置為 UTF - 8的,那麼讀取的jsp文件過程中轉碼就出現了亂碼。上面說我常常不寫 pageEncoding這個屬性,但是也沒出現過亂碼,這是怎麼回事呢?那是因為如果沒有 pageEncoding屬性中,Tomcat會採用的contentType中字符集編碼來讀取 jsp的文件,我的jsp的文件編碼通常設置為 UTF - 8編碼,字符集的contentType的也設置為使用UTF -8,這樣的Tomcat使用的UTF - 8編碼來解碼讀取的jsp的文件,二者編碼一致也不會出現亂碼。這只是中的contentType字符集的一個作用,它還有兩個作用,後面再說。可能有人會問:如果我既不設置 pageEncoding屬性,也不設置的contentType的字符集屬性,那麼 Tomcat的會採取什麼編碼來解碼讀取的jsp的文件呢?答案是異 - 8859 - 1,這是Tomcat的讀取文件採用的默認編碼,如果用這種編碼來讀取文件顯然會出現亂碼。



問題四:輸出。

問題二和問題三分析的過程其實就是從源文件àclass文件過程中的轉碼情況。最終的類文件都是以Unicode的編碼的,我們前面所做的工作就是把各種不同的編碼轉換為 Unicode的編碼,比如從 GBK的轉換為 Unicode編碼,從使用UTF - 8轉換為 Unicode的。因為只有採用正確的編碼來轉碼才能保證不出現亂碼。在jvm在運行時其內部都是採用的Unicode編碼的,其實在輸出時,又會做一次編碼的轉換。讓我們分兩種情況來討論。

1.java中採用 Sysout.out.println輸出。

比如:Sysout.out.println(“我們”)。經過正確的解碼後“我們”是unicode的保存在內存中的,但是在向標準輸出(控制台)輸出時,jvm的又做了一次轉碼,它會採用操作系統默認編碼(中文操​​作系統是GBK的),將內存中的Unicode的編碼轉換為 GBK的編碼,然後輸出到控制台。因為我們操作系統是中文系統,所以往終端顯示設備上打印字符時使用的也是GBK的編碼。因為終端的編碼無法手動改變,所以這個過程對我們來說是透明的,只要編譯時能正確轉碼,最終的輸出都將是正確的,不會出現亂碼。在Eclipse中中可以設置控制台的字符編碼,具體位置在運行配置對話框的通用標籤裡,我們可以試著設置為 UTF - 8的,此時的輸出就是亂碼了。因為輸出時是採用 GBK的編碼的,而顯示卻是使用使用UTF - 8,編碼不同,所以出現亂碼。



2.jsp中使用out.println()輸出到客戶端瀏覽器。

JSP的編譯成類後,如果輸出到客戶端,也有個轉碼的過程。爪哇會採用操作系統默認的編碼來轉碼,那麼 Tomcat的採用什麼編碼來轉碼呢?其實 tomcat的是根據 <%@網頁的語言=“爪哇”的contentType =“text / html的,字符集= UTF - 8”pageEncoding =“的UTF - 8”%>中的contentType的字符集參數來轉碼的,用來設置的contentType Tomcat的往瀏覽器發送的HTML內容所使用的編碼。Tomcat的根據這個編碼來轉碼內存中的Unicode的。經過轉碼後的Tomcat輸出到客戶端的字符編碼就是utf - 8的了。那麼瀏覽器怎麼知道採取什麼編碼格式來顯示接收到的內容呢?這就是contentType中的字符集屬性的第三個作用了:這個編碼會在HTTP的響應頭中指定以通知瀏覽器。瀏覽器使用的HTTP響應頭的contentType中的字符集屬性來顯示接收到的內容。

總結一下的contentType字符集的三個作用:

1)。在沒有 pageEncoding屬性時,Tomcat的使用它來解碼讀取的jsp的文件。

2)。Tomcat的向客戶端輸出時,使用它來編碼發送的內容。

3)。通知瀏覽器,應該以什麼編碼來顯示接收到的內容。

為了能更好的理解上面所說的解碼和轉碼過程,我們舉一個例子。

新建一個 index.jsp的文件,該文件編碼為 GBK的,在jsp的開頭我們寫上如下代碼:

<%@網頁的語言 =“爪哇”的contentType =“text / html的,字符集= UTF - 8”pageEncoding =“GBK的”%>

這裡的字符集和pageEncoding不同,但是也不會出現亂碼,我來解釋一下。首先Tomcat的讀取 jsp的內容,並根據 pageEncoding指定的GBK的編碼將讀取的GBK的字節解碼並轉換為 Unicode的字節碼保存在類文件中。然後 tomcat的在輸出時(out.println())使用字符集屬性將內存中的Unicode的轉換為 UTF - 8的編碼,並在響應頭中通知瀏覽器,瀏覽器以UTF - 8的顯示接收到的內容。整個過程沒有一次轉碼錯誤,所以就不會出現亂碼情況。



問題五:屬性和ResourceBundle的使用的解碼編碼。

以上兩個是我們常用的類,他們在讀取文件過程中並不允許我們指定解碼編碼,那麼它們採取什麼解碼方式呢?查看源碼後發現都是採用異 - 8859 - 1編碼來解碼
的。這樣的話我們也不難理解我們寫的屬性文件為什麼都是異 - 8859 - 1的了。因為採取任何一個別的編碼都將產生亂碼。因為異 - 8859 - 1編碼是沒
有中文的,所以我們輸入的中文要轉換為 unicode的,通常我們使用插件來完成,也可以使用的JDK自帶的native2ascii的工具。