工做中常常遇到java編碼問題,因爲缺少研究,老是沒法給出確切的答案,這個週末在網上查了一些資料,在此作些彙總。html
問題一:在java中讀取文件時應該採用什麼編碼?java
Java讀取文件的方式整體能夠分爲兩類:按字節讀取和按字符讀取。按字節讀取就是採用InputStream.read()方法來讀取字節,而後保存到一個byte[]數組中,最後常常用new String(byte[]);把字節數組轉換成String。在最後一步隱藏了一個編碼的細節,new String(byte[]);會使用操做系統默認的字符集來解碼字節數組,中文操做系統就是GBK。而咱們從輸入流裏讀取的字節極可能就不是GBK編碼的,由於從輸入流裏讀取的字節編碼取決於被讀取的文件自身的編碼。舉個例子:咱們在D:盤新建一個名爲demo.txt的文件,寫入」咱們。」,並保存。此時demo.txt編碼是ANSI,中文操做系統下就是GBK。此時咱們用輸入字節流讀取該文件所獲得的字節就是使用GBK方式編碼的字節。那麼咱們最終new String(byte[]);時採用平臺默認的GBK來編碼成String也是沒有問題的(字節編碼和默認解碼一致)。試想一下,若是在保存demo.txt文件時,咱們選擇UTF-8編碼,那麼該文件的編碼就不在是ANSI了,而變成了UTF-8。仍然採用輸入字節流來讀取,那麼此時讀取的字節和上一次就不同了,此次的字節是UTF-8編碼的字節。兩次的字節顯然不同,一個很明顯的區別就是:GBK每一個漢字兩個字節,而UTF-8每一個漢字三個字節。如何咱們最後還使用new String(byte[]);來構造String對象,則會出現亂碼,緣由很簡單,由於構造時採用的默認解碼GBK,而咱們的字節是UTF-8字節。正確的辦法就是使用new String(byte[],」UTF-8」);來構造String對象。此時咱們的字節編碼和構造使用的解碼是一致的,不會出現亂碼問題了。api
說完字節輸入流,再來講說字節輸出流。數組
咱們知道若是採用字節輸出流把字節輸出到某個文件,咱們是沒法指定生成文件的編碼的(假設文件之前不存在),那麼生成的文件是什麼編碼的呢?通過測試發現,其實這取決於寫入的字節編碼格式。好比如下代碼:瀏覽器
OutputStream out = new FileOutputStream("d:\\demo.txt");tomcat
out.write("咱們".getBytes());jvm
getBytes()會採用操做系統默認的字符集來編碼字節,這裏就是GBK,因此咱們寫入demo.txt文件的是GBK編碼的字節。那麼這個文件的編碼就是GBK。若是稍微修改一下程序:out.write("咱們".getBytes(「UTF-8」));此時咱們寫入的字節就是UTF-8的,那麼demo.txt文件編碼就是UTF-8。這裏還有一點,若是把」咱們」換成123或abc之類的ascii碼字符,那麼不管是採用getBytes()或者getBytes(「UTF-8」)那麼生成的文件都將是GBK編碼的。jsp
這裏能夠總結一下,InputStream中的字節編碼取決文件自己的編碼,而OutputStream生成文件的編碼取決於字節的編碼。ide
下面說說採用字符輸入流來讀取文件。工具
首先,咱們須要理解一下字符流。其實字符流能夠看作是一種包裝流,它的底層仍是採用字節流來讀取字節,而後它使用指定的編碼方式將讀取字節解碼爲字符。提及字符流,不得不提的就是InputStreamReader。如下是java api對它的說明: InputStreamReader是字節流通向字符流的橋樑:它使用指定的 charset 讀取字節並將其解碼爲字符。它使用的字符集能夠由名稱指定或顯式給定,不然可能接受平臺默認的字符集。說到這裏其實很明白了,InputStreamReader在底層仍是採用字節流來讀取字節,讀取字節後它須要一個編碼格式來解碼讀取的字節,若是咱們在構造InputStreamReader沒有傳入編碼方式,那麼會採用操做系統默認的GBK來解碼讀取的字節。還用上面demo.txt的例子,假設demo.txt編碼方式爲GBK,咱們使用以下代碼來讀取文件:
InputStreamReader in = new InputStreamReader(new FileInputStream(「demo.txt」));
那麼咱們讀取不會產生亂碼,由於文件採用GBK編碼,因此讀出的字節也是GBK編碼的,而InputStreamReader默認採用解碼也是GBK。若是把demo.txt編碼方式換成UTF-8,那麼咱們採用這種方式讀取就會產生亂碼。這是由於字節編碼(UTF-8)和咱們的解碼編碼(GBK)形成的。解決辦法以下:
InputStreamReader in = new InputStreamReader(new FileInputStream(「demo.txt」),」UTF-8」);
給InputStreamReader指定解碼編碼,這樣兩者統一就不會出現亂碼了。
下面說說字符輸出流。
字符輸出流的原理和字符輸入流的原理同樣,也能夠看作是包裝流,其底層仍是採用字節輸出流來寫文件。只是字符輸出流根據指定的編碼將字符轉換爲字節的。字符輸出流的主要類是:OutputStreamWriter。Java api解釋以下:OutputStreamWriter 是字符流通向字節流的橋樑:使用指定的 charset 將要向其寫入的字符編碼爲字節。它使用的字符集能夠由名稱指定或顯式給定,不然可能接受平臺默認的字符集。說的很明白了,它須要一個編碼將寫入的字符轉換爲字節,若是沒有指定則採用GBK編碼,那麼輸出的字節都將是GBK編碼,生成的文件也是GBK編碼的。若是採用如下方式構造OutputStreamWriter:
OutputStreamWriter out = new OutputStreamWriter(new 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裏含有中文字符,此時控制檯會出現警告信息,也出現了亂碼。究其緣由,就是由於javac採用了GBK編碼解碼咱們讀取的字節。由於咱們的字節是UTF-8編碼的,因此會出現亂碼。若是不信的話你能夠本身試試。那麼解決辦法呢?解決辦法就是使用javac的encoding參數來制定咱們的解碼編碼。以下:javac -encoding UTF-8 Demo.java。這裏咱們指定了使用UTF-8來解碼讀取的字節,因爲這個編碼和Demo.java文件編碼一致,因此不會出現亂碼狀況了。
2.Eclipse中編譯java文件。
我習慣把Eclipse的編碼設置成UTF-8。那麼每一個項目中的java源文件的編碼就是UTF-8。這樣編譯也從沒有問題,也沒有出現過亂碼。正是由於這樣才掩蓋了使用javac可能出現的亂碼。那麼Eclipse是如何正確編譯文件編碼爲UTF-8的java源文件的呢?惟一的解釋就是Eclipse自動識別了咱們java源文件的文件編碼,而後採起了正確的encoding參數來編譯咱們的java源文件。功勞都歸功於IDE的強大了。
3.使用Ant來編譯java文件。
Ant也是我經常使用的編譯java文件的工具。首先,必須知道Ant在後臺其實也是採用javac來編譯java源文件的,那麼可想而知,1會出現的問題在Ant中也會存在。若是咱們使用Ant來編譯UTF-8編碼的java源文件,而且不指定如何編碼,那麼也會出現亂碼的狀況。因此Ant的編譯命令<javac>有一個屬性」 encoding」容許咱們指定編碼,若是咱們要編譯源文件編碼爲UTF-8的java文件,那麼咱們的命令應該以下:
<javac destdir="${classes}" target="1.4" source="1.4" deprecation="off" debug="on" debuglevel="lines,vars,source" optimize="off" encoding="UTF-8">
指定了編碼也就至關於」javac –encoding」了,因此不會出現亂碼了。
問題三:tomcat中編譯jsp的狀況。
這個話題也是由問題二引出的。既然javac編譯java源文件須要採用正確的編碼,那麼tomcat編譯jsp時也要讀取文件,此時tomcat採用什麼編碼來讀取文件?會出現亂碼狀況嗎?下面咱們來分析。
咱們一般會在jsp開頭寫上以下代碼:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>
我經常不寫pageEncoding這個屬於,也不明白它的做用,可是不寫也沒出現過亂碼狀況。其實這個屬性就是告訴tomcat採用什麼編碼來讀取jsp文件的。它應該和jsp文件自己的編碼一致。好比咱們新建個jsp文件,設置文件編碼爲GBK,那麼此時咱們的pageEncoding應該設置爲GBK,這樣咱們寫入文件的字符就是GBK編碼的,tomcat讀取文件時採用也是GBK編碼,因此能保證正確的解碼讀取的字節。不會出現亂碼。若是把pageEncoding設置爲UTF-8,那麼讀取jsp文件過程當中轉碼就出現了亂碼。上面說我經常不寫pageEncoding這個屬性,可是也沒出現過亂碼,這是怎麼回事呢?那是由於若是沒有pageEncoding屬性,tomcat會採用contentType中charset編碼來讀取jsp文件,個人jsp文件編碼一般設置爲UTF-8,contentType的charset也設置爲UTF-8,這樣tomcat使用UTF-8編碼來解碼讀取的jsp文件,兩者編碼一致也不會出現亂碼。這只是contentType中charset的一個做用,它還有兩個做用,後面再說。可能有人會問:若是我既不設置pageEncoding屬性,也不設置contentType的charset屬性,那麼tomcat會採起什麼編碼來解碼讀取的jsp文件呢?答案是iso-8859-1,這是tomcat讀取文件採用的默認編碼,若是用這種編碼來讀取文件顯然會出現亂碼。
問題四:輸出。
問題二和問題三分析的過程其實就是從源文件àclass文件過程當中的轉碼狀況。最終的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中能夠設置控制檯的字符編碼,具體位置在Run Configuration對話框的Common標籤裏,咱們能夠試着設置爲UTF-8,此時的輸出就是亂碼了。由於輸出時是採用GBK編碼的,而顯示倒是使用UTF-8,編碼不一樣,因此出現亂碼。
2.jsp中使用out.println()輸出到客戶端瀏覽器。
Jsp編譯成class後,若是輸出到客戶端,也有個轉碼的過程。Java會採用操做系統默認的編碼來轉碼,那麼tomcat採用什麼編碼來轉碼呢?其實tomcat是根據<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>中contentType的charset參數來轉碼的,contentType用來設置tomcat往瀏覽器發送HTML內容所使用的編碼。Tomcat根據這個編碼來轉碼內存中的unicode。通過轉碼後tomcat輸出到客戶端的字符編碼就是utf-8了。那麼瀏覽器怎麼知道採起什麼編碼格式來顯示接收到的內容呢?這就是contentType的charset屬性的第三個做用了:這個編碼會在HTTP響應頭中指定以通知瀏覽器。瀏覽器使用http響應頭的contentType的charset屬性來顯示接收到的內容。
總結一下contentType charset的三個做用:
1).在沒有pageEncoding屬性時,tomcat使用它來解碼讀取的jsp文件。
2).tomcat向客戶端輸出時,使用它來編碼發送的內容。
3).通知瀏覽器,應該以什麼編碼來顯示接收到的內容。
爲了能更好的理解上面所說的解碼和轉碼過程,咱們舉一個例子。
新建一個index.jsp文件,該文件編碼爲GBK,在jsp開頭咱們寫上以下代碼:
<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="GBK"%>
這裏的charset和pageEncoding不一樣,可是也不會出現亂碼,我來解釋一下。首先tomcat讀取jsp內容,並根據pageEncoding指定的GBK編碼將讀取的GBK字節解碼並轉換爲unicode字節碼保存在class文件中。而後tomcat在輸出時(out.println())使用charset屬性將內存中的unicode轉換爲utf-8編碼,並在響應頭中通知瀏覽器,瀏覽器以utf-8顯示接收到的內容。整個過程沒有一次轉碼錯誤,因此就不會出現亂碼狀況。
問題五:Properties和ResourceBundle使用的解碼編碼。
以上兩個是咱們經常使用的類,他們在讀取文件過程當中並不容許咱們指定解碼編碼,那麼它們採起什麼解碼方式呢?查看源碼後發現都是採用 iso-8859-1 編碼來解碼