Java讀取文件的方式總體可以分為兩類:按字節(jié)讀取和按字符讀取。按字節(jié)讀取就是采用InputStream.read()方法來讀取字節(jié),,然后保存到一個(gè)byte[]數(shù)組中,,最后經(jīng)常用new String(byte[]);把字節(jié)數(shù)組轉(zhuǎn)換成String。在最后一步隱藏了一個(gè)編碼的細(xì)節(jié),,new String(byte[]);會使用操作系統(tǒng)默認(rèn)的字符集來解碼字節(jié)數(shù)組,,中文操作系統(tǒng)就是GBK。而我們從輸入流里讀取的字節(jié)很可能就不是GBK編碼的,,因?yàn)閺妮斎肓骼镒x取的字節(jié)編碼取決于被讀取的文件自身的編碼,。舉個(gè)例子:我們在D:盤新建一個(gè)名為demo.txt的文件,寫入”我們,。”,,并保存。此時(shí)demo.txt編碼是ANSI,,中文操作系統(tǒng)下就是GBK,。此時(shí)我們用輸入字節(jié)流讀取該文件所得到的字節(jié)就是使用GBK方式編碼的字節(jié)。那么我們最終new String(byte[]);時(shí)采用平臺默認(rèn)的GBK來編碼成String也是沒有問題的(字節(jié)編碼和默認(rèn)解碼一致),。試想一下,,如果在保存demo.txt文件時(shí),我們選擇UTF-8編碼,,那么該文件的編碼就不在是ANSI了,,而變成了UTF-8,。仍然采用輸入字節(jié)流來讀取,那么此時(shí)讀取的字節(jié)和上一次就不一樣了,,這次的字節(jié)是UTF-8編碼的字節(jié),。兩次的字節(jié)顯然不一樣,一個(gè)很明顯的區(qū)別就是:GBK每個(gè)漢字兩個(gè)字節(jié),,而UTF-8每個(gè)漢字三個(gè)字節(jié),。如何我們最后還使用new String(byte[]);來構(gòu)造String對象,則會出現(xiàn)亂碼,,原因很簡單,,因?yàn)闃?gòu)造時(shí)采用的默認(rèn)解碼GBK,而我們的字節(jié)是UTF-8字節(jié),。正確的辦法就是使用new String(byte[],”UTF-8”);來構(gòu)造String對象,。此時(shí)我們的字節(jié)編碼和構(gòu)造使用的解碼是一致的,不會出現(xiàn)亂碼問題了,。 說完字節(jié)輸入流,,再來說說字節(jié)輸出流。 我們知道如果采用字節(jié)輸出流把字節(jié)輸出到某個(gè)文件,,我們是無法指定生成文件的編碼的(假設(shè)文件以前不存在),,那么生成的文件是什么編碼的呢?經(jīng)過測試發(fā)現(xiàn),,其實(shí)這取決于寫入的字節(jié)編碼格式,。比如以下代碼: OutputStream out = new FileOutputStream("d:\\demo.txt"); out.write("我們".getBytes()); getBytes()會采用操作系統(tǒng)默認(rèn)的字符集來編碼字節(jié),這里就是GBK,,所以我們寫入demo.txt文件的是GBK編碼的字節(jié),。那么這個(gè)文件的編碼就是GBK,。如果稍微修改一下程序:out.write("我們".getBytes(“UTF-8”));此時(shí)我們寫入的字節(jié)就是UTF-8的,,那么demo.txt文件編碼就是UTF-8。這里還有一點(diǎn),,如果把”我們”換成123或abc之類的ascii碼字符,,那么無論是采用getBytes()或者getBytes(“UTF-8”)那么生成的文件都將是GBK編碼的。 這里可以總結(jié)一下,,InputStream中的字節(jié)編碼取決文件本身的編碼,,而OutputStream生成文件的編碼取決于字節(jié)的編碼。 下面說說采用字符輸入流來讀取文件,。 首先,,我們需要理解一下字符流。其實(shí)字符流可以看做是一種包裝流,,它的底層還是采用字節(jié)流來讀取字節(jié),,然后它使用指定的編碼方式將讀取字節(jié)解碼為字符,。說起字符流,不得不提的就是InputStreamReader,。以下是java api對它的說明: InputStreamReader 那么我們讀取不會產(chǎn)生亂碼,因?yàn)槲募捎?/span>GBK編碼,,所以讀出的字節(jié)也是GBK編碼的,,而InputStreamReader默認(rèn)采用解碼也是GBK。如果把demo.txt編碼方式換成UTF-8,那么我們采用這種方式讀取就會產(chǎn)生亂碼,。這是因?yàn)樽止?jié)編碼(UTF-8)和我們的解碼編碼(GBK)造成的,。解決辦法如下: InputStreamReader 給InputStreamReader指定解碼編碼,這樣二者統(tǒng)一就不會出現(xiàn)亂碼了,。 下面說說字符輸出流,。 字符輸出流的原理和字符輸入流的原理一樣,也可以看做是包裝流,,其底層還是采用字節(jié)輸出流來寫文件,。只是字符輸出流根據(jù)指定的編碼將字符轉(zhuǎn)換為字節(jié)的。字符輸出流的主要類是:OutputStreamWriter,。Java api解釋如下:OutputStreamWriter OutputStreamWriter out = new OutputStreamWriter(new FileOutputStream(“dd.txt”),”UTF-8”); 那么寫入的字符將被編碼為UTF-8的字節(jié),生成的文件也將是UTF-8格式的,。 問題二: 問題三:tomcat中編譯jsp的情況。 <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%> 我常常不寫pageEncoding這個(gè)屬于,,也不明白它的作用,,但是不寫也沒出現(xiàn)過亂碼情況。其實(shí)這個(gè)屬性就是告訴tomcat采用什么編碼來讀取jsp文件的,。它應(yīng)該和jsp文件本身的編碼一致,。比如我們新建個(gè)jsp文件,設(shè)置文件編碼為GBK,那么此時(shí)我們的pageEncoding應(yīng)該設(shè)置為GBK,這樣我們寫入文件的字符就是GBK編碼的,,tomcat讀取文件時(shí)采用也是GBK編碼,,所以能保證正確的解碼讀取的字節(jié)。不會出現(xiàn)亂碼,。如果把pageEncoding設(shè)置為UTF-8,,那么讀取jsp文件過程中轉(zhuǎn)碼就出現(xiàn)了亂碼。上面說我常常不寫pageEncoding這個(gè)屬性,但是也沒出現(xiàn)過亂碼,,這是怎么回事呢,?那是因?yàn)槿绻麤]有pageEncoding屬性,tomcat會采用contentType中charset編碼來讀取jsp文件,,我的jsp文件編碼通常設(shè)置為UTF-8,contentType的charset也設(shè)置為UTF-8,這樣tomcat使用UTF-8編碼來解碼讀取的jsp文件,,二者編碼一致也不會出現(xiàn)亂碼。這只是contentType中charset的一個(gè)作用,,它還有兩個(gè)作用,,后面再說??赡苡腥藭枺喝绻壹炔辉O(shè)置pageEncoding屬性,,也不設(shè)置contentType的charset屬性,那么tomcat會采取什么編碼來解碼讀取的jsp文件呢,?答案是iso-8859-1,,這是tomcat讀取文件采用的默認(rèn)編碼,如果用這種編碼來讀取文件顯然會出現(xiàn)亂碼,。 問題二和問題三分析的過程其實(shí)就是從源文件àclass文件過程中的轉(zhuǎn)碼情況。最終的class文件都是以unicode編碼的,,我們前面所做的工作就是把各種不同的編碼轉(zhuǎn)換為unicode編碼,,比如從GBK轉(zhuǎn)換為unicode,從UTF-8轉(zhuǎn)換為unicode。因?yàn)橹挥胁捎谜_的編碼來轉(zhuǎn)碼才能保證不出現(xiàn)亂碼,。Jvm在運(yùn)行時(shí)其內(nèi)部都是采用unicode編碼的,,其實(shí)在輸出時(shí),又會做一次編碼的轉(zhuǎn)換,。讓我們分兩種情況來討論,。 1.java中采用Sysout.out.println輸出。 比如:Sysout.out.println(“我們”),。經(jīng)過正確的解碼后”我們”是unicode保存在內(nèi)存中的,,但是在向標(biāo)準(zhǔn)輸出(控制臺)輸出時(shí),jvm又做了一次轉(zhuǎn)碼,,它會采用操作系統(tǒng)默認(rèn)編碼(中文操作系統(tǒng)是GBK),,將內(nèi)存中的unicode編碼轉(zhuǎn)換為GBK編碼,然后輸出到控制臺,。因?yàn)槲覀儾僮飨到y(tǒng)是中文系統(tǒng),,所以往終端顯示設(shè)備上打印字符時(shí)使用的也是GBK編碼。因?yàn)榻K端的編碼無法手動改變,,所以這個(gè)過程對我們來說是透明的,,只要編譯時(shí)能正確轉(zhuǎn)碼,,最終的輸出都將是正確的,,不會出現(xiàn)亂碼,。在Eclipse中可以設(shè)置控制臺的字符編碼,具體位置在Run Configuration對話框的Common標(biāo)簽里,我們可以試著設(shè)置為UTF-8,此時(shí)的輸出就是亂碼了,。因?yàn)檩敵鰰r(shí)是采用GBK編碼的,,而顯示卻是使用UTF-8,編碼不同,,所以出現(xiàn)亂碼,。 2.jsp中使用out.println()輸出到客戶端瀏覽器。 Jsp編譯成class后,,如果輸出到客戶端,,也有個(gè)轉(zhuǎn)碼的過程。Java會采用操作系統(tǒng)默認(rèn)的編碼來轉(zhuǎn)碼,,那么tomcat采用什么編碼來轉(zhuǎn)碼呢,?其實(shí)tomcat是根據(jù)<%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="utf-8"%>中contentType的charset參數(shù)來轉(zhuǎn)碼的,contentType用來設(shè)置tomcat往瀏覽器發(fā)送HTML內(nèi)容所使用的編碼,。Tomcat根據(jù)這個(gè)編碼來轉(zhuǎn)碼內(nèi)存中的unicode,。經(jīng)過轉(zhuǎn)碼后tomcat輸出到客戶端的字符編碼就是utf-8了。那么瀏覽器怎么知道采取什么編碼格式來顯示接收到的內(nèi)容呢,?這就是contentType的charset屬性的第三個(gè)作用了:這個(gè)編碼會在HTTP響應(yīng)頭中指定以通知瀏覽器,。瀏覽器使用http響應(yīng)頭的contentType的charset屬性來顯示接收到的內(nèi)容。 總結(jié)一下contentType charset的三個(gè)作用: 1).在沒有pageEncoding屬性時(shí),,tomcat使用它來解碼讀取的jsp文件,。 2).tomcat向客戶端輸出時(shí),使用它來編碼發(fā)送的內(nèi)容,。 3).通知瀏覽器,,應(yīng)該以什么編碼來顯示接收到的內(nèi)容。 為了能更好的理解上面所說的解碼和轉(zhuǎn)碼過程,,我們舉一個(gè)例子,。 新建一個(gè)index.jsp文件,該文件編碼為GBK,在jsp開頭我們寫上如下代碼: <%@ page language="java" contentType="text/html; charset=utf-8" pageEncoding="GBK"%> 這里的charset和pageEncoding不同,,但是也不會出現(xiàn)亂碼,,我來解釋一下。首先tomcat讀取jsp內(nèi)容,,并根據(jù)pageEncoding指定的GBK編碼將讀取的GBK字節(jié)解碼并轉(zhuǎn)換為unicode字節(jié)碼保存在class文件中,。然后tomcat在輸出時(shí)(out.println())使用charset屬性將內(nèi)存中的unicode轉(zhuǎn)換為utf-8編碼,并在響應(yīng)頭中通知瀏覽器,,瀏覽器以utf-8顯示接收到的內(nèi)容,。整個(gè)過程沒有一次轉(zhuǎn)碼錯(cuò)誤,所以就不會出現(xiàn)亂碼情況。 |
|