Web開發的中文問題一直困惑大家,尤其是對于初上手者。這次有機會徹底解決研究了一下中文亂碼的原因和解決方案,做個總結。
為什么會有中文亂碼?
因為在默認情況下,HTTP的包都是以“8859_1”來編碼的(沒辦法,誰叫這些標準都是老美定的)。“8859_1”是西文編碼方式,對于英文字母沒有任何問題,但是對于中文就不行了。所以,如果不做任何設定,直接將中文用“8859_1”來編碼傳遞,那結果必然是亂碼。
解決思路是什么?
好在老美還是有國際化眼光的,HTTP包的編碼方式可以由用戶指定。因此,只要事先指定好用相對應的編碼方式來對傳遞內容(比如表單提交的中文等)進行編碼,就可以順利解決亂碼的問題。
兩個基本概念
在進入具體的解決方法之前,首先要對兩個基本概念作一下解釋。對于由表單提交的內容,HTTP有兩種傳遞方式,分別是“GET”方式和“POST”方式。
“GET”方式就是將各參數直接通過HTTP的包頭(head)來傳遞,簡而言之就是直接通過我們所熟悉的網址(URL)來傳遞,所以我們經常能看到的在一個網址后面跟著許多復雜的由“?”和“&”構成的字符串,其實這就是需要傳遞的參數了。
“POST”方式則是將所需傳遞的參數包在HTTP的正文(body)中來傳遞。因此通過“POST”方式來進行傳遞,在瀏覽器的網址上面什么都看不見。
因此,相比較而言,“POST”隱蔽性較好;而“GET”方式使用起來比較容易,直接寫URL就可以了。
綜上所述,不難發現,解決中文亂碼問題實際上就變為對這兩種HTTP傳遞的編碼方式進行適當的設定。當然,從解決問題的難易以及對系統架構的完美性角度著手,又分為以下三個層次:
1)入門方法,在所有的servlet和jsp中堆設定用的代碼。
2)中級方法,對web伺服器進行配置。
3)高級方法,編寫filter過濾器,對“POST”和“GET”獨立過濾處理。
下面就具體描述各解決方法:
1)入門方法,在所有的servlet和jsp中“堆”寫設定用的代碼。
所謂入門方法,那就是現實十分簡單,當然效果也是很好的。只是必須在每個相應的文件中寫相同的設定代碼,代碼的重復性就比較大。
由前面所述,由于“POST”和“GET”方式的不同,因此對應著兩種的設定方式也不同。
“POST”的情況下,如果服務器端腳本是一個servlet,那只要在doPost()方法里面插入一句
request.setCharacterEncode("GB2312");
需要注意的是,這句設定必須在所有從request對象做提取操作之前執行,如果類似于request.getParameter()的操作在前,那么系統將使用默認的“8859_1”編碼方式,而忽略后面的設定代碼。
如果服務器端是一個jsp腳本,那只要在該腳本的jsp申明部分做好設定即可:
<%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding="gb2312"%>
如果是“GET”方式,也就是想通過URL來傳遞中文的話,稍微要麻煩些,首先因為瀏覽器地址欄是不支持中文的,也就是如果直接將中文放置在超級連接里面是無效的。因此需要在發送端對中文內容進行編碼,比如:
URLEncoder.encoder("http://localhost/submit?name=張三","UTF-8");
“UTF-8”表示用這種編碼方式對原字符串進行編碼,編碼好之后看到的結果是
http://localhost/submit?name=%D5%C5%C8%FD
所以我們經常看到在瀏覽器里面有眾多的類似與“%D5%C5%C8%FD”這樣的字符串,就是表明被UTF-8編碼過了。由于UTF-8是跨各種平臺的通用編碼方式,因此比較常用于各種語言文字的傳輸載體。
相對應的,在接受方需要進行反向的解碼即可,代碼如下:
new String( request.getParameter("name").getBytes("8859_1"), "gb2312" );
這里可能會有一些疑問,為什么用“8859_1”來解碼。事實上,我在第一次嘗試的時候也曾使用“UTF-8”來嘗試解碼,結果出現亂碼失敗。究其原因,盡管“張三”被編碼成了“%D5%C5%C8%FD”來傳輸,但是在傳輸過程中,“%D5%C5%C8%FD”仍舊需要由“8859_1”來編碼打包成HTTP,因此,在接收端,自然先需要由“8859_1”來還原到“%D5%C5%C8%FD”的“UTF-8”格式,然后再由“UTF-8”還原到“GB2312”。
所以這樣也不難理解為什么所謂“瀏覽器地址欄是不支持中文”,不能直接用中文而要用“UTF-8”來通過“8859_1”來打包了,原因就是“%D5%C5%C8%FD”這串類似于密碼般的字符串本身就是西文字符,用“8859_1”編解碼沒有任何問題。而中文由于是2byte一個漢字,直接用西文方式來編解碼自然就會出現問題。這也就是為什么稱“UTF-8”為“跨各種平臺的通用編碼方式”了。
背景小資料:
由于“UTF-8”是通用編碼方式,因此所有的語言格式均可以轉換為“UTF-8”,在日益國際的今天,多語言的系統要求越來越多,因此強烈建議使用“UTF-8”來做為系統統一的編解碼方式,從而徹底解決中文亂碼的問題。
“UTF-8”為了能做到兼容所有語言的編解碼,因此每一個字符均用2個byte來編碼。這樣就造成了存西文字符時需要多一倍的空間。這也算是為了通用而付出的代價了。
2)中級方法,對web伺服器進行配置
可想而知,相對于“堆”寫大量代碼,配置一下web伺服器config文件來解決中文亂碼問題就顯得優雅許多。但是由于各種web伺服器的情況不同,其配置方法也不盡相同。因此,其兼容性是個比較大的問題。
這里列舉一下,如何通過修改Tomcat的conf配置文件來解決中文亂碼的問題。
找到Tomcat的配置文件server.xml中的Connector這一行,為其添加一個如下的屬性
tomcat4 中 get 與 post 的編碼是一樣的,所以只要在過濾器中通過 request.setCharacterEncoding 設定一次就可以解決 get 與 post 的問題。
然而,在 tomcat5 中,get 與 post 的處理是分開進行的,對get的處理通過 前面的URIEncoding進行處理,對post的內容依然通過 request.setCharacterEncoding 處理,為了保持兼容,就有了這個設定(useBodyEncodingForURI="true" 使用與 Body 一樣的編碼來處理 URI, 這個設定是為了與 tomcat4保持兼容)。
3)高級方法,編寫filter過濾器,對“POST”和“GET”獨立過濾處理。
高級方法,顧名思義,就是可以脫離于任何平臺,同時又免去冗余的隊旗代碼工作的解決方案——編寫過濾器,Filter。
首先編寫一個過濾器SetCharacterEncodingFilter
public class SetCharacterEncodingFilter implements Filter {
/**
* The default character encoding to set for requests that pass through
* this filter.
*/
protected String encoding = null;
/**
* The filter configuration object we are associated with. If this value
* is null, this filter instance is not currently configured.
*/
protected FilterConfig filterConfig = null;
/**
* Should a character encoding specified by the client be ignored?
*/
protected boolean ignore = true;
// --------------------------------------------------------- Public Methods
/**
* Take this filter out of service.
*/
public void destroy() {
this.encoding = null;
this.filterConfig = null;
}
/**
* Select and set (if specified) the character encoding to be used to
* interpret request parameters for this request.
*
* @param request The servlet request we are processing
* @param result The servlet response we are creating
* @param chain The filter chain we are processing
*
* @exception IOException if an input/output error occurs
* @exception ServletException if a servlet error occurs
*/
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain)
throws IOException, ServletException {
// Conditionally select and set the character encoding to be used
if (ignore || (request.getCharacterEncoding() == null)) {
String encoding = selectEncoding(request);
if(encoding != null){
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
if(httpServletRequest.getMethod().toLowerCase().equals("post")){
//如果是POST方法
request.setCharacterEncoding(encoding);
}
else{
//如果是GET方法
//非常抱歉,我還有沒有找到很好的對應get方法的代碼
//一旦完成了這部分代碼,馬上添加在這里。
//!·#¥%……—*()——+|
}
}
}
// Pass control on to the next filter
chain.doFilter(request, response);
}
/**
* Place this filter into service.
*
* @param filterConfig The filter configuration object
*/
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
this.encoding = filterConfig.getInitParameter("encoding");
String value = filterConfig.getInitParameter("ignore");
if (value == null)
this.ignore = true;
else if (value.equalsIgnoreCase("true"))
this.ignore = true;
else if (value.equalsIgnoreCase("yes"))
this.ignore = true;
else
this.ignore = false;
}
// ------------------------------------------------------ Protected Methods
/**
* Select an appropriate character encoding to be used, based on the
* characteristics of the current request and/or filter initialization
* parameters. If no character encoding should be set, return
* <code>null</code>.
* <p>
* The default implementation unconditionally returns the value configured
* by the <strong>encoding</strong> initialization parameter for this
* filter.
*
* @param request The servlet request we are processing
*/
protected String selectEncoding(ServletRequest request) {
return (this.encoding);
}
}
編寫完過濾器以后,需要對其進行部署,也就是在web.xml中做個配置:
在<display-name>標簽之后,添加:
<filter>
<filter-name>Set Character Encoding</filter-name>
<filter-class>com.zavax.utility.filters.SetCharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF8</param-value>
</init-param>
<init-param>
<param-name>ignore</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Set Character Encoding</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>