二、JSP文件中硬編碼中文字符,在瀏覽器上顯示亂碼。
我們用eclipse編寫一個(gè)JSP頁(yè)面,使用tomcat瀏覽這個(gè)頁(yè)面時(shí),整個(gè)頁(yè)面的中文字符都是亂碼。這是什么原因呢?
JSP頁(yè)面從編寫到在瀏覽器上瀏覽,總共有四次字符編解碼。
1. 以某種字符編碼保存JSP文件
2. Tomcat以指定編碼(需要與1中編碼一樣)來(lái)讀取JSP文件并編譯
3. Tomcat向?yàn)g覽器以指定編碼來(lái)發(fā)送HTML內(nèi)容
4. 瀏覽器以指定編碼解析HTML內(nèi)容
這里的四次字符編解碼,有一次發(fā)生錯(cuò)誤最終顯示的就會(huì)是亂碼。我們依次來(lái)分析各次的字符編碼是如何設(shè)置的。
- 保存JSP文件,這是在編輯器中設(shè)置的,比如eclipse中,設(shè)置文件字符類型為utf-8。
- JSP文件開(kāi)頭的《%@ page language=“java” contentType=“text/html; charset=utf-8” pageEncoding=“utf-8”%》,其中pageEncoding用來(lái)告訴tomcat此文件所用的字符編碼。這個(gè)編碼應(yīng)該與eclipse保存文件用的編碼一致。Tomcat以此編碼方式來(lái)讀取JSP文件并編譯。
- page標(biāo)簽中的contentType用來(lái)設(shè)置tomcat往瀏覽器發(fā)送HTML內(nèi)容所使用的編碼。這個(gè)編碼會(huì)在HTTP響應(yīng)頭中指定以通知瀏覽器。
- 瀏覽器根據(jù)HTTP響應(yīng)頭中指定的字符編碼來(lái)解析HTML內(nèi)容。如:
HTTP/1.1 200 OK
Date: Mon, 01 Sep 2008 23:13:31 GMT
Server: Apache/2.2.4 (Win32) mod_jk/1.2.26
Vary: Host,Accept-Encoding
Set-Cookie: JAVA2000_STYLE_ID=1; Domain=www.java2000.net; Expires=Thu, 03-Nov-2011 09:00:10 GMT; Path=/
Content-Encoding: gzip
Transfer-Encoding: chunked
Content-Type: text/html;charset=UTF-8
另外,HTML中有個(gè)標(biāo)簽《meta http-equiv=“Content-Type” content=“text/html; charset=UTF-8”》中也指定了charset。不過(guò)這個(gè)字符編碼只有在當(dāng)網(wǎng)頁(yè)保存在本地作為靜態(tài)網(wǎng)頁(yè)時(shí)有效,因?yàn)闆](méi)有HTTP頭,所以瀏覽器根據(jù)此標(biāo)簽來(lái)識(shí)別HTML內(nèi)容的編碼方式。
現(xiàn)在在JSP文件中硬編碼出現(xiàn)亂碼的機(jī)會(huì)比較小了,因?yàn)榇蠹叶加昧巳鏴clipse的編輯器,基本上可以自動(dòng)保證這幾個(gè)編碼設(shè)置的正確性。現(xiàn)在更多碰到的是在JSP文件中從其他數(shù)據(jù)源中讀取中文字符所產(chǎn)生的亂碼問(wèn)題。
在JSP文件中讀取字符文件并在頁(yè)面中顯示,中文字符顯示為亂碼。
比如,我們?cè)贘SP文件中使用以下代碼:
《%
BufferedReader reader = new BufferedReader(new FileReader(“D:\\test.txt”));
String content = reader.readLine();
reader.close();
%》
《%=content%》
test.txt里保存的是中文字符,但在瀏覽器上看到的亂碼。這是個(gè)經(jīng)常見(jiàn)到的問(wèn)題。我們繼續(xù)用之前的方法一步步來(lái)分析輸入和輸出流
1. test.txt是以某種編碼方式保存中文字符,比如UTF-8。
2. BufferedReader直接讀取test.txt的字節(jié)內(nèi)容并以默認(rèn)方式構(gòu)造字符串。分析BufferedReader的代碼,我們可以看到 BufferedReader調(diào)用了FileReader的read方法,而FileReader又調(diào)用了FileInputStream的native 的read方法。所謂native的方法,就是操作系統(tǒng)底層方法。那么我們操作系統(tǒng)是中文系統(tǒng),所以FileInputStream默認(rèn)用GBK方式讀取 文件。因?yàn)槲覀儽4鎡est.txt用的是UTF-8,所以在這里讀取文件內(nèi)容使用的是GBK,是錯(cuò)誤的編碼。
3. 《%=content%》其實(shí)就是out.print(content),這里又用到了HTTP的輸出流JspWriter,于是字符串content又被以JSP的page標(biāo)簽中指定的UTF-8方式編碼成字節(jié)數(shù)組被發(fā)送到瀏覽器端。
4. 瀏覽器以HTTP頭中指定的方式解碼字符,這時(shí)無(wú)論是用GBK還是UTF-8解碼,顯示的都是亂碼。
可見(jiàn),我們字符編碼轉(zhuǎn)換在第二步時(shí)出錯(cuò)了,UTF-8的字符串被當(dāng)做GBK讀入了內(nèi)存中。
解決這個(gè)亂碼問(wèn)題有兩種方法,一是把test.txt用GBK保存,則FileInputStream能正確讀入中文字符;二是使用InputStreamReader來(lái)轉(zhuǎn)換字符編碼,如:
InputStreamReader sr = new InputStreamReader(new FileInputStream("D:\\test.txt"),"utf-8");
BufferedReader reader = new BufferedReader(sr);
這樣,JAVA就會(huì)用utf-8的方式來(lái)從文件中讀取字符數(shù)據(jù)。
另外,我們可以通過(guò)在java命令后帶上Dfile.encoding參數(shù)來(lái)指定虛擬機(jī)讀取文件使用的默認(rèn)字符編碼,例如java -Dfile.encoding=utf-8 Test,這樣,我們?cè)贘AVA代碼里用System.getProperty(“file.encoding”)取到的值為utf-8。
四、JSP讀取request.getParameter里的中文參數(shù)后,在頁(yè)面顯示為亂碼。
在JAVA的WEB應(yīng)用中,對(duì)request對(duì)象里的parameters的中文處理一直是常見(jiàn)也最難搞的一只大怪獸。經(jīng)常是剛搞定了這邊,那邊又出了亂 碼。而導(dǎo)致這種復(fù)雜性的,主要是此過(guò)程中字符編解碼次數(shù)非常多,而且無(wú)論是瀏覽器還是WEB服務(wù)器特別是TOMCAT總是不能給我們一個(gè)比較滿意的支持。
首先我們來(lái)分析用GET方式上傳參數(shù)的亂碼情況。
例如我們?cè)跒g覽器地址欄輸入以下URL:http://localhost:8080/test/test.jsp?param=大家好
我們的JSP代碼如此處理param這個(gè)參數(shù):
《% String text = request.getParameter(“param”); %》
《%=text%》
而就這么簡(jiǎn)單的兩句代碼,我們很有可能在頁(yè)面上看到這樣的亂碼:?ó????
網(wǎng)上對(duì)處理request.getParamter中的亂碼有很多文章和方法,也都是正確的,只是方法太多讓人一直不明白到底是為什么。這里給大家分析一下到底是怎么一回事。
首先,我們來(lái)看看與request對(duì)象有哪些相關(guān)的編碼設(shè)置:
1. JSP文件的字符編碼
2. 請(qǐng)求這個(gè)帶參數(shù)URL的源頁(yè)面的字符編碼
3. IE的高級(jí)設(shè)置中的選項(xiàng)“總以u(píng)tf-8方式發(fā)送URL地址”
4. TOMCAT的server.xml中配置URIEncoding
5. 函數(shù)request.setCharacterEncoding()
6. JS的encodeURIComponent函數(shù)與JAVA的URLDecoder類
這么多條相關(guān)編碼設(shè)置,也難怪大家被搞得頭暈了。這里給大家根據(jù)各種情況給大家一一分析一下。見(jiàn)下表:
序號(hào) | 請(qǐng)求源頁(yè)面編碼 | 從地址欄輸入U(xiǎn)RL訪問(wèn) | Tomcate的UrlEncoding設(shè)置 | IE的UTF-8發(fā)送Url地址設(shè)置 | 結(jié)果 |
1 | UTF-8 | 未設(shè)置 | 打開(kāi) | 顯示符號(hào)亂碼 | |
2 | UTF-8 | 未設(shè)置 | 關(guān)閉 | 顯示符號(hào)亂碼 | |
3 | GBK | 未設(shè)置 | 打開(kāi) | 顯示符號(hào)亂碼 | |
4 | GBK | 未設(shè)置 | 關(guān)閉 | 顯示符號(hào)亂碼 | |
5 | 地址欄輸入 | 未設(shè)置 | 打開(kāi) | 顯示符號(hào)亂碼 | |
6 | 地址欄輸入 | 未設(shè)置 | 關(guān)閉 | 顯示符號(hào)亂碼 | |
7 | UTF-8 | GBK | 打開(kāi) | 顯示符號(hào)亂碼 | |
8 | UTF-8 | GBK | 關(guān)閉 | 顯示符號(hào)亂碼 | |
9 | GBK | GBK | 打開(kāi) | 正常 | |
10 | GBK | GBK | 關(guān)閉 | 正常 | |
11 | 地址欄輸入 | GBK | 打開(kāi) | 正常 | |
12 | 地址欄輸入 | GBK | 關(guān)閉 | 正常 | |
13 | UTF-8 | UTF-8 | 打開(kāi) | IE6:奇數(shù)個(gè)的中文最后一位為亂碼 IE7:正常 | |
14 | UTF-8 | UTF-8 | 關(guān)閉 | IE6:奇數(shù)個(gè)的中文最后一位為亂碼 IE7:正常 | |
15 | 地址欄輸入 | UTF-8 | 打開(kāi) | 顯示口字亂碼 | |
16 | 地址欄輸入 | UTF-8 | 關(guān)閉 | 顯示口字亂碼 | |
17 | GBK | UTF-8 | 打開(kāi) | 顯示問(wèn)好亂碼 | |
18 | GBK | UTF-8 | 關(guān)閉 | 顯示問(wèn)好亂碼 | |
19 | 地址欄輸入 | UTF-8 | 打開(kāi) | 顯示口字亂碼 | |
20 | 地址欄輸入 | UTF-8 | 關(guān)閉 | 顯示口字亂碼 |
以上表格里的現(xiàn)象,除了指名在IE7上,其他全是在IE6上測(cè)試的結(jié)果。
由這個(gè)表我們可以看到,IE的“總以u(píng)tf-8方式發(fā)送URL地址”設(shè)置并不影響對(duì)parameter的解析,而從頁(yè)面請(qǐng)求URL和從地址欄輸入U(xiǎn)RL居然也有不同的表現(xiàn)。
根據(jù)這個(gè)表列出的現(xiàn)象,大家只要用smartSniff抓幾個(gè)網(wǎng)絡(luò)包,并稍稍調(diào)查一下TOMCAT的源代碼,就可以得出以下結(jié)論:
1. IE設(shè)置中的“總以u(píng)tf-8方式發(fā)送URL地址”只對(duì)URL的PATH部分起作用,對(duì)查詢字符串是不起作用的。也就是說(shuō),如果勾選了這個(gè)選項(xiàng),那么類似http://localhost:8080/test/大家好.jsp?param=大家好這種URL,前一個(gè)“大家好”將被轉(zhuǎn)化成utf-8形式,而后一個(gè)并沒(méi)有變化。這里所說(shuō)的utf-8形式,其實(shí)應(yīng)該叫utf-8+escape形式,即%B4%F3%BC%D2%BA%C3這種形式。
那么,查詢字符串中的中文字符,到底是用什么編碼傳送到服務(wù)器的呢?答案是系統(tǒng)默認(rèn)編碼,即GBK。也就是說(shuō),在我們中文操作系統(tǒng)上,傳送給WEB服務(wù)器的查詢字符串,總是以GBK來(lái)編碼的。
2. 在頁(yè)面中通過(guò)鏈接或location重定向或open新窗口的方式來(lái)請(qǐng)求一個(gè)URL,這個(gè)URL里面的中文字符是用什么編碼的?
答:是用該頁(yè)面的編碼類型。也就是說(shuō),如果我們從某個(gè)源JSP頁(yè)面上的鏈接來(lái)訪問(wèn)http://localhost:8080/test/test.jsp?param=大家好這個(gè)URL,如果源JSP頁(yè)面的編碼是UTF-8,則大家好這幾個(gè)字的編碼就是UTF-8。
而在地址欄上直接輸入U(xiǎn)RL地址,或者從系統(tǒng)剪貼板粘貼到地址欄上,這個(gè)輸入并非從頁(yè)面中發(fā)起的,而是由操作系統(tǒng)發(fā)起的,所以這個(gè)編碼只可能是系統(tǒng)的默認(rèn) 編碼,與任何頁(yè)面無(wú)關(guān)。我們還發(fā)現(xiàn),在不同的瀏覽器上,用鏈接方式打開(kāi)的頁(yè)面,如果在地址欄上再敲個(gè)回車,顯示的結(jié)果也會(huì)不同。IE上敲回車后顯示不變 化,而傲游上可能就會(huì)有亂碼或亂碼消失的變化。說(shuō)明IE上敲回車,實(shí)際發(fā)送的是之前記憶下來(lái)的內(nèi)存中的URL,而傲游上發(fā)送的從當(dāng)前地址欄重新獲取的 URL。
3. TOMCAT的URIEncoding如果不加以設(shè)置,則默認(rèn)使用ISO-8859-1來(lái)解碼URL,設(shè)置后便用設(shè)置了的編碼方式來(lái)解碼。這個(gè)解碼同時(shí)包 括PATH部分和查詢字符串部分??梢?jiàn),這個(gè)參數(shù)是對(duì)用GET方式傳遞的中文參數(shù)最關(guān)鍵的設(shè)置。不過(guò),這個(gè)參數(shù)只對(duì)GET方式傳遞的參數(shù)有效,對(duì)POST 的無(wú)效。分析TOMCAT的源代碼我們可以看到,在請(qǐng)求一個(gè)頁(yè)面時(shí),TOMCAT會(huì)嘗試構(gòu)造一個(gè)Request對(duì)象,在這個(gè)對(duì)象里,會(huì)從 Server.xml里讀取URIEncoding的值,并賦值給Parameters類的queryStringEncoding變量,而這個(gè)變量將在 解析request.getParameter中的GET參數(shù)時(shí)用來(lái)指導(dǎo)字符解碼。
4. request.setCharacterEncoding函數(shù)只對(duì)POST的參數(shù)有效,對(duì)GET的參數(shù)無(wú)效。且這個(gè)函數(shù)必須是在第一次調(diào)用 request.getParameter之前使用。這是因?yàn)镻arameters類有兩個(gè)字符編碼參數(shù),一個(gè)是encoding,另一個(gè)是 queryStringEncoding,而setCharacterEncoding設(shè)置的是encoding,這個(gè)是在解析POST的參數(shù)是才用到 的。
所以,這就導(dǎo)致了我們通常都要分開(kāi)處理POST和GET的字符編碼,用TOMCAT自帶的filter只能處理POST的,另外要設(shè)置URIEncoding來(lái)設(shè)置GET的。這樣很麻煩而且URIEncoding無(wú)法根據(jù)內(nèi)容來(lái)動(dòng)態(tài)區(qū)分編碼,總還是一個(gè)問(wèn)題。
在調(diào)查TOMCAT的代碼時(shí)發(fā)現(xiàn)了另一個(gè)在server.xml里的參數(shù)useBodyEncodingForURI,可以解決這個(gè)問(wèn)題。這個(gè)參數(shù)設(shè)成 true后,TOMCAT就會(huì)用request.setCharacterEncoding所設(shè)置的字符編碼來(lái)同樣解析GET參數(shù)了。這樣,那個(gè) SetCharacterEncodingFilter就可以同時(shí)處理GET和POST參數(shù)了。
知道了以上知識(shí)后,我們?cè)賮?lái)分析一下前面表格中列出的幾個(gè)典型現(xiàn)象。
第一條,請(qǐng)求源頁(yè)面的編碼為UTF-8,而TOMCAT的URIEncoding未指定,則TOMCAT用ISO8859-1方式來(lái)解碼參數(shù),所以從request中讀出來(lái)后,內(nèi)存中存儲(chǔ)的為錯(cuò)誤的UNICODE數(shù)據(jù),導(dǎo)致之后到屏幕顯示的所有轉(zhuǎn)換全部出錯(cuò)。
第二條,請(qǐng)求源頁(yè)面編碼為GBK,而TOMCAT的URIEncoding也為GBK,TOMCAT用GBK方式去解碼塬本用GBK編碼的字符,解碼正確,內(nèi)存中的UNICODE值正確,最終顯示正確的中文。
第三條,請(qǐng)求源頁(yè)面編碼為UTF-8,TOMCAT的URIEncoding也為UTF-8,而在IE6中最終顯示的中文字符,如果是奇數(shù)個(gè)數(shù),則最后一個(gè)會(huì)顯示為亂碼。這是為什么呢?
我的猜測(cè)是,這是因?yàn)镮E6將URL地址發(fā)送時(shí),對(duì)查詢字符串是直接對(duì)UTF-8格式的字符使用GBK來(lái)編碼,而不是對(duì)UNICODE的字符來(lái)用GBK編 碼,所以UTF-8的數(shù)據(jù)沒(méi)有經(jīng)過(guò)UNICODE而直接編碼成了GBK。而到了TOMCAT這邊,GBK的編碼又被當(dāng)成UTF-8做了解碼。所以這個(gè)過(guò)程 中經(jīng)過(guò)了UTF-8轉(zhuǎn)換成GBK,然后又從GBK轉(zhuǎn)換成UTF-8的過(guò)程,而這種轉(zhuǎn)換,恰好就會(huì)出現(xiàn)奇數(shù)個(gè)中文字符串的最后一位為亂碼的現(xiàn)象。而在IE7 中,估計(jì)把這種現(xiàn)象當(dāng)做BUG已經(jīng)被解決了,即在發(fā)送地址時(shí)會(huì)先轉(zhuǎn)成UNICODE再編碼成GBK。那么估計(jì)在IE7的瀏覽器+中文操作系統(tǒng)環(huán)境下,如果 我們把TOMCAT的URIEncoding設(shè)置成GBK,無(wú)論JSP編碼成什么格式,都不會(huì)出現(xiàn)亂碼。這個(gè)沒(méi)測(cè)試,請(qǐng)大家自己驗(yàn)證。
其他幾條就不再做分析了,有興趣的大家自己分析。
五、對(duì)URL做Encode和Decode
對(duì)于request參數(shù)的中文亂碼問(wèn)題,個(gè)人覺(jué)得最好的還是用URLEncode/URLDecode,因?yàn)槿绻愕腤EB站點(diǎn)要支持國(guó)際化,最好就是保證從IE遞送過(guò)來(lái)的參數(shù)永遠(yuǎn)是正確的UTF-8編碼。
在IE端,我們可以用JS腳本來(lái)對(duì)參數(shù)編碼:encodeURIComponent(),編碼后中文字符便變成了%B4%F3%BC%D2%BA%C3這 種形式。在JAVA端,可以用java.net.URLDecoder.decode來(lái)解碼。不過(guò)這里要注意一個(gè)問(wèn)題,就是TOMCAT會(huì)自動(dòng)先對(duì)URL 做一次decode,我們可以在TOMCAT的UDecoder類中看到這一點(diǎn)。不過(guò) TOMCAT并非使用了URLDecoder.decode,而是自己編寫了一個(gè)decode函數(shù)。網(wǎng)上有些文章上介紹過(guò)一種處理亂碼的方法便是在JS中對(duì)參數(shù)做兩次encodeURIComponent,在JAVA中做 一次decode,可以解決一些沒(méi)有設(shè)置URIEncoding時(shí)發(fā)生的亂碼問(wèn)題。不過(guò)個(gè)人覺(jué)得如果弄懂了整個(gè)字符編碼轉(zhuǎn)換的過(guò)程,基本上是用不到這種方法的。
六、從數(shù)據(jù)庫(kù)中讀取中文字符數(shù)據(jù),在頁(yè)面上顯示為亂碼。
對(duì)于數(shù)據(jù)庫(kù)中讀取中文字符出現(xiàn)亂碼的問(wèn)題,本人遇到的還比較少,所以暫時(shí)沒(méi)有總結(jié)。如果大家有類似的經(jīng)驗(yàn),歡迎補(bǔ)充說(shuō)明,我一定注明作者身份。
好了,對(duì)各種字符亂碼問(wèn)題的分析就總結(jié)到這里,相信只要把握“以指定編碼讀取--轉(zhuǎn)換為UNICODE--以指定編碼輸入”這基本步驟,初學(xué)者也可以很快 分析出字符亂碼的根源所在。另外我建議不要隨便使用new String(str.getBytes(enc1),enc2)這種方式來(lái)強(qiáng)行轉(zhuǎn)碼,也不要隨便使用網(wǎng)上的字符轉(zhuǎn)碼函數(shù),我覺(jué)得只會(huì)把問(wèn)題隱藏更深更復(fù) 雜化。我們應(yīng)該清晰地分析整個(gè)字符流的編解碼過(guò)程,自然可以找出亂碼的根源所在,從而保證整個(gè)字符流動(dòng)中,在內(nèi)存中的UNICODE始終是正確的。
聯(lián)系客服