在基于J2EE的B/S應用中,中文亂碼是一個永恒的主題,永遠都無法回避。誠然對于一般的程序員,我們沒有必要對編碼進行深刻的研究。但是至少我們需要了解:
①編碼基礎
②String的getBytes([encoding])方法內幕
③String的toCharArray()方法內幕
④輸出時的編碼與亂碼原因
⑤UTF-8的編碼規則和GBK如何轉換到UTF-8
⑥字符在各種表現形式下的值
⑦native2ascii命令的用法
正因為Java中采用了Unicode編碼作為中介,所以任何初始的輸入和最終的輸出都會有:
①從byte[]----》encode字符---》Unicode的輸入轉換
②從Unicode---》encode字符---》byte[]的輸出轉換
一個典型的J2EE B/S應用,從客戶端輸出到最終服務器端的輸出,需要經歷如下的流程
其中,客戶端包括了:操作系統、Web瀏覽器、靜態網頁、JS等。服務器端包括了:Servlet、JSP、Filter、配置文件等。這中間的任何一步配置不當或轉換不當都有可能導致亂碼或者?的出現。下面就將針對各個流程環節中可能出現的亂碼地方,做一次全面的總結并給出基本的對策:
【客戶端】
①操作系統
對于Windows操作系統,中文語言的設置在控制面板中進行
如果把最后的“高級”中改成英文(美國),你會發現很多不支持多國語言的軟件,菜單或工具欄的中文立馬變成?了。
對于Linux系統,臨時的設置可以通過下面的設置來指定系統使用中文環境和UTF-8編碼格式

如果是需要永久性的起作用,必須在 /home/<user>/.bashrc文件中設置
②瀏覽器
現代的瀏覽器都可以動態的設置瀏覽頁面的編碼
【服務器端】
①服務器的配置文件
對于Tomcat,可以在server.xml文件中進行配置



其中黃色高亮部分就是將請求的內容使用UTF-8進行編碼。但要注意:這只是針對Get請求的!如果要連Post請求的內容都使用UTF-8編碼,那么就要使用下面的過濾器了。
②過濾器
它是在請求未到達對應的servlet或JSP之前進行攔截,然后通過設置request的character encoding來一次性進行轉碼。










































③HTTP Request封裝器
在上面的filter代碼中我們見到了一句注釋:HttpRequestWrapper req = new HttpRequestWrapper(hreq);這是下面要介紹的一種方法。如果學習過設計模式我們會知道所謂的“Wrapper”類,其實都是一個Decorator/Proxy模式的實現。即通過在“Wrapper”類中包含一個對象,然后暴露和該對象同樣方法的接口,在客戶端調用該對象的方法之前做一些手腳。JDK中的動態代理DynamicProxy原理就和這個類似。
在Servlet的API中有一個類:HttpServletRequestWrapper。如果查看API我們會發現有趣的地方:
原來這個類也是實現了HttpServletRequest接口的。于是乎我們可以通過創建一個繼續于該類的“Wrapper”,然后覆蓋HttpServletRequest的getParameter(key)和getParameters()方法,在這兩個方法返回字符串之前,進行重新編碼。














































于是我們可以修改一些前面的filter,去掉那句注釋和下面的request.setCharacterEncoding("UTF-8")。為什么呢?因為我們知道reuqest.getParameters(...)方法是對GET和POST請求同樣的,現在我們已經在每次獲取參數值之前做了轉換,所以不論是GET還是POST請求,都可以正常的轉碼了。
要記住:request.setCharacterEncoding(...)方法只對將數據保存在HTTP信息體中的POST請求有效,對于將數據直接粘附與URL后面的GET請求是無效的。但是不論GET還是POST請求,他們都是通過request.getParameter(...)來獲取參數的。這其實是一個一勞永逸的方法!
④Servlet
如果在前面我們正確地設置了Tomcat,過濾器,封裝器,那么對于Servlet來說,要獲取參數就是簡單的request.getParameter(PARAM_NAME);了,但是如果沒有設置過濾器或封裝器,就只能手工地調用下面的語句:
String tmp = request.getParameter(PARAM_NAME);
String param = new String(tmp.getBytes("ISO-8859-1"), encoding);
通常來說這個encoding就是你應用的編碼,亦是JSP頁面的編碼。如果JSP頁面是GBK,而你的代碼是UTF-8,那么用UTF-8做encoding就肯定亂碼了。此時就得用上前面所說的GBK轉UTF-8方法,得到原始字節后進行擴展,使之成為UTF-8格式的字符數組再進行解碼了
PS:上面這種情況是經常出現的。特別是在你要抓取別人的網頁內容時,對方可能使用的是GBK/GB2312編碼,而你的Web應用是UTF-8編碼。
在解決完請求的亂碼問題之后,接下來要解決的就是響應的亂碼問題了。響應的亂碼問題通常發生于響應內容的編碼和JSP頁面,HTML網頁的編碼不同,所以在前面的過濾器中我們一并把這個問題給解決了:

或者調用

來看看這個用法和HttpServletRequest.setCharacterEncoding有什么區別吧!
Sets the character encoding (MIME charset) of the response being sent to the client, for example, to UTF-8. If the character encoding has already been set by setContentType(java.lang.String) or setLocale(java.util.Locale), this method overrides it. Calling setContentType(java.lang.String) with the String of text/html and calling this method with the String of UTF-8 is equivalent with calling setContentType with the String of text/html; charset=UTF-8.
This method can be called repeatedly to change the character encoding. This method has no effect if it is called after getWriter has been called or after the response has been committed.
和前者不同,這個方法可以在getWriter()被調用后再調用,而且可以被多次調用。而request.setCharacterEncoding只能在request.getParameter之前調用。
其次就是這個方法的優先級是最高的。如果之前已經通過setContentType或setLocale指定編碼,再次調用該方法會覆蓋之前的配置。
⑤JSP
在JSP頁面中,和編碼相關的幾個參數有
A.contentType
B.pageEncoding
這兩個參數有什么不同呢?contentType是用來告訴瀏覽器,當接收到服務器傳輸回來的響應體內容后要用什么編碼解析,這個是沒有疑問的。但pageEncoding呢?
我們知道JSP頁面在運行時會被容器編譯成.class文件,對于文件必然有“文件編碼”這一說。對了!pageEncoding就是告訴容器要以什么方式讀入JSP文件。如果你的頁面有中文字符或其他雙字節字符,那么就必須用UTF-8,GBK,Big5等支持雙字節的字符集了。
值得注意的是,雖然容器以pageEncoding指定的方式讀入JSP文件,但在最終編譯成.class文件后,還是以Unicode保存的。例如JSP頁面的"中文"二字,在.class文件中會以"\u4e2d;\u6587;"保存。
⑥HTML
如果說我們可以通過response.setContentType或page指令來指定動態頁面的編碼格式,那么對于HTML這樣靜態的頁面就無能為力了。于是就得使用另外一種方式了:

⑦屬性配置文件
Java的國際化(i18N)是很多Web應用必備的功能之一,對于菜單文字,提示信息我們都可以用.properties的方式保存,但是在保存之前我們需要將內容轉換成unicode的值,于是就要使用native2ascii命令了。具體可以參考前面的【Java基礎專題】編碼與亂碼(07)---native2ascii命令的用法
【工具配置】
在團隊合作中,由于每個人的開發環境不一致,通常會出現一個人本來可以正常顯示中文的JSP文件或者Java文件,到了另外一個人的機器上就變成亂碼了。通常這都是由于編輯器的編碼不一致而造成的。以Eclipse為例:
在Dreamweaver中,我們可以這樣設置/改變頁面的編碼
注意:在團隊開發中,明確開發環境的配置時通常會忽略IDE編碼的選擇,導致大量不可逆的亂碼情況出現。應該值得注意
良好的編程習慣,對編碼和數據傳輸流程的清晰認識,規范的配置是確保JavaEE應用不會出現亂碼的三大法寶
-------------------------------------------------------------
生活就像打牌,不是要抓一手好牌,而是要盡力打好一手爛牌。