深度揭祕亂碼問題背後的原因及解決方式
做Web開發的IT人,如果工作中沒遇到過幾次亂碼的問題,估計都不好意思說自己是開發工程師。 :)
而亂碼問題也是各種各樣,有儲存到資料庫中是亂碼的,有在服務端接收到引數是亂碼的,有在後臺返回到客戶端時候出現亂碼的……
這形形色色的亂碼問題,如果處理起來不得要領,著實會讓開發人員費不少工夫。
本文,將從亂碼的產生原因,應用伺服器內部對引數的處理各方面詳解原理及解決方案。
1編碼
在開發中,只要有IO的地方,都會涉及到編碼。例如下面的程式碼:
System.out.println( "Hello 中國" );
你猜這個會輸出什麼呢?
這個其實是和你的檔案編碼有很大關係的,可能會輸出
Hello 中國
也有可能會輸出成下面這個樣子
Hello ���
你可以改動IDE中的檔案編碼重複試幾次。那為什麼會產生這個原因呢?
我們來看這行程式碼執行過程中的呼叫棧:
我們看到,簡單的一句輸出,也是有編碼的。
看看CharsetEncoder,實現類真多啊
再比如我們都無比熟悉的equals方法,先宣告常量STR如下:
你認為,這個時候會有輸出嗎?
答案是看情況!
當我把Constant類以UTF-8為編碼儲存後,把包含if邏輯的程式碼以GBK儲存之後,equals執行時比較的兩個引數就變成了下面的樣子:
所以這一定是不會為true的。
這也是亂碼產生的原因,
原因即解碼時採用的encoding與編碼時用的encoding不一致所造成的。
2 應用伺服器內的亂碼
在Web應用中,亂碼的成因和上述分析是一致的。
我們嚮應用伺服器傳送一個這樣的請求:
http://localhost:8080/test/servlet?abc=你好
伺服器用request.getParameter("abc")來獲取,這個時候有亂碼問題嗎?
答案依然是It depends.
這次的看情況是要看哪些情況呢?有以下這些。
Tomcat的版本
是否單獨設定通道的編碼
是否有使用統一的編碼Filter
Tomcat預設對於不同的通道(Connector),都可以獨立設定相關的編碼屬性,例如預設是下面這樣的配置:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />
而我們可以增加自定義的編碼配置:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" **URIEncoding**="UTF-8"/>
紅色的URIEncoding即為新增的屬性,這個引數是和Tomcat的版本有關係的。
在Tomcat8中,其對應的官方文件是這樣說明的:
This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, UTF-8 will be used unless the org.apache.catalina.STRICT_SERVLET_COMPLIANCE
system property is set to
true
in which case ISO-8859-1 will be used.
也就是不設定
-Dorg.apache.catalina.STRICT_SERVLET_COMPLIANCE=true
那預設的編碼會採用UTF-8。
但是在Tomcat的8.0之前版本官方文件裡是這樣寫的說明:
This specifies the character encoding used to decode the URI bytes, after %xx decoding the URL. If not specified, ISO-8859-1 will be used.
也就是不特殊指定,預設將使用ISO-8859-1進行編碼。
看Connector的構造方法中,也是明確按此進行配置的
public Connector(String protocol) {setProtocol(protocol);...**//注意下面的程式碼**if (!Globals.STRICT_SERVLET_COMPLIANCE) {URIEncoding = "UTF-8";URIEncodingLower = URIEncoding.toLowerCase(Locale.ENGLISH);}}
這個配置又是如何作用於引數解析的呢?看下面
public void service(org.apache.coyote.Request req,org.apache.coyote.Response res)throws Exception {// Set query string encodingreq.getParameters().setQueryStringEncoding(connector.getURIEncoding());
//注意這裡,具體去設定的是Parameters類的queryStringEncoding,這個屬性會在後面解析URL中包含的引數時用到。}
public void setQueryStringEncoding( String s ) {queryStringEncoding=s;}
而具體引數處理時,傳進去的就是這個queryStringEncoding
processParameters( decodedQuery, queryStringEncoding );
這種情況,在Tomcat8中就不需要再顯示的配置URIEncoding了,而之前的版本則需要配置。有上面的程式碼參照,我們看到,對於URL中傳入的引數,除了設定URIEncoding這個配置之外,是沒有辦法保證的。因為其解析引數時使用的是queryStringEncoding這個引數,因此只有才保證傳到Tomcat的引數編碼和解碼正確了。
而如果配置了統一的編碼過濾器,則過濾器內設定request的編碼一定要在解析引數前,即呼叫getParameter前設定,否則並不生效。這是因為parameter只會解析一次,之後就放到一個List中直接根據key返回了。
那是不是設定一個統一的編碼Filter,一切就萬事大吉了呢?
答案還是看情況吧?
恭喜,你會搶答啦!
3JSP亂碼問題
我們都知道在jsp中,可以設定這樣一個jsp頭
<%@ page contentType="text/html;charset=iso-8859-1" language="java" %>
那這個時候如果你的頁面中要輸出一些返回的中文資料,這個時候,頁面妥妥的出現了亂碼。原因自然是iso-859-1不支援中文有關。注意這裡charset不寫依然是按iso-8859-1為預設值。
這時,你想到了Filter。在Filter中你大膽的設定了
resp.setCharacterEncoding(encoding);
這個時候,頁面展示卻依然華麗的亂碼了。擦,這是啥原因?
那這個時候,在jsp中顯示資料的時候,依然還是會出現亂碼的,此時注意觀察下響應頭:
看下面的程式碼,由於response在輸出的時候。會獲取設定的encoding
/** * Return the writer associated with this Response. * * @exception IllegalStateException if <code>getOutputStream</code> has * already been called for this response * @exception IOException if an input/output error occurs */@Overridepublic PrintWriter getWriter()throws IOException {if (usingOutputStream) {throw new IllegalStateException(sm.getString("coyoteResponse.getWriter.ise"));}if (ENFORCE_ENCODING_IN_GET_WRITER) {/* * If the response's character encoding has not been specified as * described in <code>getCharacterEncoding</code> (i.e., the method * just returns the default value <code>**ISO-8859-1**</code>), * <code>getWriter</code> updates it to <code>ISO-8859-1</code> * (with the effect that a subsequent call to getContentType() will * include a charset=ISO-8859-1 component which will also be * reflected in the Content-Type response header, thereby satisfying * the Servlet spec requirement that containers must communicate the * character encoding used for the servlet response's writer to the * client). */ **setCharacterEncoding**(getCharacterEncoding());}usingWriter = true;outputBuffer.checkConverter();if (writer == null) {writer = new CoyoteWriter(outputBuffer);}return writer;}
而這個encoding是什麼設定的呢?
public void setContentType(String type) {
if (isCommitted()) {return;}if (SecurityUtil.isPackageProtectionEnabled()){AccessController.doPrivileged(new SetContentTypePrivilegedAction(type));} else {response.**setContentType**(type); //這裡在設定contentType,由於配置中同時包含charset}}
String[] m = MEDIA_TYPE_CACHE.parse(type); //這個是在解析contentType引數if (m == null) {// Invalid - Assume no charset and just pass through whatever // the user provided. coyoteResponse.setContentTypeNoCharset(type);return;}coyoteResponse.setContentTypeNoCharset(m[0]);if (m[1] != null) {// Ignore charset if getWriter() has already been called if (!usingWriter) {coyoteResponse.setCharacterEncoding(m[1]); //這裡就用解析出來的引數設定。isCharacterEncodingSet = true;}}
而我們一般為了處理這種亂碼問題統一寫的filter,在請求處理前就先把request和response的encoding都設定好。而這裡預設提供的charset為ISO-8859-1,就出了亂碼問題了。另外一個在處理JSP時容易出的問題,就是contentType中指定的charset和filter中已經設定的encoding,兩者不一致,比如你的jsp中忘記改了,使用的是預設的ISO-8859-1.此時,先通過filter設定的encoding會先於contentType的設定執行,因此,依然會出現亂碼問題。
4總結
在本文中,深入分析了亂碼背後產生的原因:編碼和解碼時採用的encoding不一致。而解決問題的最樸素的道理就是保持多種資料來源編碼的一致性,無論是資料庫的,檔案的,還是輸入輸出的,都採用一致的編碼,可以簡少很多問題。另外,許多檔案中有一些預設編碼,開發中可能不太注意,此處也是容易出現問題的地方。
相關文章
- PDF複製亂碼 -- 原因及解決方案
- 解決SSH亂碼問題
- 解決中文亂碼問題
- vi/vim配置篇:亂碼產生的原因及解決
- 解決 plsql 遇到亂碼的問題SQL
- oracle字元亂碼問題的解決Oracle字元
- 解決Flex裡的亂碼問題Flex
- SpringBoot整合Redis亂碼原因及解決方案Spring BootRedis
- 解決ajax get post方式提交中文引數亂碼問題
- MySql中文亂碼問題解決MySql
- Jmeter 解決中文亂碼問題JMeter
- Java 解決中文亂碼問題Java
- RDSSQLSERVER解決中文亂碼問題SQLServer
- 解決MySQL中文亂碼問題MySql
- MYSQL亂碼問題解決方法MySql
- 揭祕.NET Core剪裁器背後的技術
- 從XML檔案亂碼問題,探尋其背後的原理XML
- 解決confluence的亂碼問題
- DES加密中文亂碼問題的解決加密
- CentOS中文亂碼問題的解決方法CentOS
- 揭祕:遊戲買量市場日益艱難的深度原因遊戲
- 揭祕電子遊戲背後音效製作的故事遊戲
- java中亂碼問題解決方法Java
- cat 輸出亂碼問題解決
- 解決plsql中中文亂碼問題SQL
- TongWeb下亂碼問題解決思路Web
- 解決使用Git Bash亂碼問題Git
- plsql查詢亂碼問題解決SQL
- 解決Mysql匯入亂碼問題MySql
- 徹底解決程式亂碼問題
- springmvc 解決中文亂碼問題SpringMVC
- js解決url中文亂碼問題JS
- Sublime Text 3 中文亂碼問題的解決
- Homestead 使用問題及解決方式
- flashfxp 亂碼,2種辦法解決flashfxp 亂碼問題
- URL地址中的中文亂碼問題的解決
- webView的使用及其亂碼問題的解決方案WebView
- 近期工作遇到的問題及解決方式收藏