工作中經常遇到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對象。此時我們的字節編碼和構造使用的解碼是一致的,不會出現亂碼問題瞭。
說完字節輸入流,再來說說字節輸出流。
我們知道如果采用字節輸出流把字節輸出到某個文件,我們是無法指定生成文件的編碼的(假設文件以前不存在),那麼生成的文件是什麼編碼的呢?經過測試發現,其實這取決於寫入的字節編碼格式。比如以下代碼:
OutputStream out = new FileOutputStream(“d:\demo.txt”);
out.write(“我們”.getBytes());
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編碼的。
這裡可以總結一下,InputStream中的字節編碼取決文件本身的編碼,而OutputStream生成文件的編碼取決於字節的編碼。
下面說說采用字符輸入流來讀取文件。
首先,我們需要理解一下字符流。其實字符流可以看做是一種包裝流,它的底層還是采用字節流來讀取字節,然後它使用指定的編碼方式將讀取字節解碼為字符。說起字符流,不得不提的就是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這個屬性,但是也沒出現過亂碼,這是