Java亂碼解決之道

1.常見字符編碼
ASCII編碼:

ASCII,American Standard Code for Information Interchange,是基於拉丁字母的一套電腦編碼系統,主要用於顯示現代英語和其餘西歐語言。它是現今最通用的單字節編碼系統。javascript

 
ASCII碼使用指定的7位或者8爲二進制數字組合表示128或者256種可能的字符。標準的ASCII編碼使用的是7(2^7 = 128)位二進制數來表示全部的大小寫字母、數字和標點符號已經一些特殊的控制字符,最前面的一位統一規定爲0。其中0~31及127(共33個)是控制 字符或通訊專用字符,32~126(共95個)是字符(32是空格),其中48~57爲0到9十個阿拉伯數字,65~90爲26個大寫英文字 母,97~122號爲26個小寫英文字母,其他爲一些標點符號、運算符號等。
 
GBK***編碼:

ASCII最大的缺點就是顯示字符有限,他雖然解決了部分西歐語言的顯示問題,可是對更多的其餘語言他實在是無能爲了。隨着計算機技術的發展,使用 範圍愈來愈普遍了,ASCII的缺陷愈來愈明顯了,其餘國家和地區須要使用計算機,必需要設計一套符合本國/本地區的編碼規則。例如爲了顯示中文,咱們就 必需要設計一套編碼規則用於將漢字轉換爲計算機能夠接受的數字系統的數。html

GB2312,用於漢字處理、 漢字通訊等系統之間的信息交換,通行於中國大陸。它的編碼規則是:小於127的字符的意義與原來相同,但兩個大於127的字符連在一塊兒時,就表示一個漢 字,前面的一個字節(他稱之爲高字節)從0xA1用到 0xF7,後面一個字節(低字節)從0xA1到0xFE,這樣咱們就能夠組合出大約7000多個簡體漢字了。雖然GB2312收錄了這麼多漢子,他所覆蓋 的使用率能夠達到99%,可是對於那些不常見的漢字,例如人名、地名、古漢語,它就不能處理了,因而就有下面的GBK、GB 18030的出現。(點擊GB2312簡體中文編碼表查看)。java

GB18030全 稱:國家標準GB 18030-2005《信息技術 中文編碼字符集》,是我國計算機系統必須遵循的基礎性標準之一,GB18030有兩個版本:GB18030-2000和GB18030-2005。 GB18030-2000是GBK的取代版本,它的主要特色是在GBK基礎上增長了CJK統一漢字擴充A的漢字。程序員

GB 18030主要有如下特色:web

    與UTF-8相同,採用多字節編碼,每一個字能夠由1個、2個或4個字節組成。chrome

    編碼空間龐大,最多可定義161萬個字符。數據庫

    支持中國國內少數民族的文字,不須要動用造字區。apache

 
    漢字收錄範圍包含繁體漢字以及日韓漢字。

GBK,漢字編碼標準之一,全稱《漢字內碼擴展規範》,它 向下與 GB 2312 編碼兼容,向上支持 ISO 10646.1 國際標準,是前者向後者過渡過程當中的一個承上啓下的標準。它的編碼範圍以下圖:瀏覽器

 
Unicode編碼:

正如前面前面所提到的同樣,世界存在這麼多國家,也存在着多種編碼風格,像中文的GB23二、GBK、GB18030,這樣亂搞一套,雖然在本地運行沒有問題,可是一旦出如今網絡上,因爲互不兼容,訪問則會出現亂碼。爲了解決這個問題,偉大的Unicode編碼騰空出世。tomcat

Unicode編碼的做用就是可以使計算機實現誇平臺、跨語言的文本轉換和處理。它幾乎包含了世界上全部的符號,而且每一個符號都是獨一無二的。在它的編碼世界裏,每個數字表明一個符號,每個符號表明瞭一個數字,不存在二義性。

 
Unicode編碼又稱統一碼、萬國碼、單一碼,它是業界的一種標準,是爲了解決傳統的字符編碼方案的侷限而產生的,它爲每種語言中的每一個字符設定 了統一而且惟一的二進制編碼,以知足跨語言、跨平臺進行文本轉換、處理的要求。同時Unicode是字符集,它存在不少幾種實現方式如:UTF-八、 UTF-16.
UTF-8:

互聯網的普及,強烈要求出現一種統一的編碼方式。UTF-8就是在互聯網上使用最廣的一種unicode的實現方式。其餘實現方式還包括UTF-16和UTF-32,不過在互聯網上基本不用。重複一遍:UTF-8是Unicode的實現方式之一。

 
UTF-8最大的一個特色,就是它是一種變長的編碼方式。它可使用1~4個字節表示一個符號,根據不一樣的符號而變化字節長度。
UTF-8的編碼規則很簡單,只有兩條:
1)對於單字節的符號,字節的第一位設爲0,後面7位爲這個符號的unicode碼。所以對於英語字母,UTF-8編碼和ASCII碼是相同的。
2)對於n字節的符號(n>1),第一個字節的前n位都設爲1,第n+1位設爲0,後面字節的前兩位一概設爲10。剩下的沒有說起的二進制位,所有爲這個符號的unicode碼。
 
 
編碼&&編碼格式:
首先先看看java編碼類圖 [1]
 
首先根據指定的chart設置ChartSet類,而後根據ChartSet建立ChartSetEncoder對象,最後再調用 CharsetEncoder.encode 對字符串進行編碼,不一樣的編碼類型都會對應到一個類中,實際的編碼過程是在這些類中完成的。下面時序圖展現詳細的編碼過程:
 
經過這編碼的類圖和時序圖能夠了解編碼的詳細過程。下面將經過一段簡單的代碼對ISO-8859-一、GBK、UTF-8編碼:
public class Test02 {
    public static void main(String[] args) throws UnsupportedEncodingException {
        String string = "我是 cm";
        Test02.printChart(string.toCharArray());
        Test02.printChart(string.getBytes("ISO-8859-1"));
        Test02.printChart(string.getBytes("GBK"));
        Test02.printChart(string.getBytes("UTF-8"));
    }
   
    /**
     * char轉換爲16進制
     */
    public static void printChart(char[] chars){
        for(int i = 0 ; i < chars.length ; i++){
            System.out.print(Integer.toHexString(chars[i]) + " "); 
        }
        System.out.println("");
    }
   
    /**
     * byte轉換爲16進制
     */
    public static void printChart(byte[] bytes){
        for(int i = 0 ; i < bytes.length ; i++){
            String hex = Integer.toHexString(bytes[i] & 0xFF); 
             if (hex.length() == 1) { 
               hex = '0' + hex; 
             } 
             System.out.print(hex.toUpperCase() + " "); 
        }
        System.out.println("");
    }
}
-------------------------outPut:
6211 662f 20 63 6d 
3F 3F 20 63 6D 
CE D2 CA C7 20 63 6D 
E6 88 91 E6 98 AF 20 63 6D
 

經過程序咱們能夠看到「我是 cm」的結果爲:

char[]:6211 662f 20 63 6d

ISO-8859-1:3F 3F 20 63 6D
GBK:CE D2 CA C7 20 63 6D
UTF-8:E6 88 91 E6 98 AF 20 63 6D

 
圖以下:
 
 
 
 
JAVAWEB中的編碼&&解碼:
 
 

客戶端想服務器發送請求無非就經過四中狀況:

一、URL方式直接訪問。

二、頁面連接。

三、表單get提交

 
四、表單post提交
 

能夠看到各大瀏覽器對「我是」的編碼狀況以下:

 

path部分

Query String

Firefox

E6 88 91 E6 98 AF

E6 88 91 E6 98 AF

Chrome

E6 88 91 E6 98 AF

E6 88 91 E6 98 AF

IE

E6 88 91 E6 98 AF

CE D2 CA C7

查閱上篇博客的編碼可知對於path部分Firefox、chrome、IE都是採用UTF-8編碼格式,對於Query String部分Firefox、chrome採用UTF-8,IE採用GBK。至於爲何會加上%,這是由於URL的編碼規範規定瀏覽器將ASCII字 符非 ASCII 字符按照某種編碼格式編碼成 16 進制數字而後將每一個 16 進製表示的字節前加上「%」。

固然對於不一樣的瀏覽器,相同瀏覽器不一樣版本,不一樣的操做系統等環境都會致使編碼結果不一樣,上表某一種狀況,對於URL編碼規則下任何結論都是過早 的。因爲各大瀏覽器、各個操做系統對URL的URI、QueryString編碼均可能存在不一樣,這樣對服務器的解碼勢必會形成很大的困擾,下面咱們將已 tomcat,看tomcat是如何對URL進行解碼操做的。

解析請求的 URL 是在 org.apache.coyote.HTTP11.InternalInputBuffer 的 parseRequestLine 方法中,這個方法把傳過來的 URL 的 byte[] 設置到 org.apache.coyote.Request 的相應的屬性中。這裏的 URL 仍然是 byte 格式,轉成 char 是在 org.apache.catalina.connector.CoyoteAdapter 的 convertURI 方法中完成的:

protected void convertURI(MessageBytes uri, Request request) throws Exception { ByteChunk bc = uri.getByteChunk(); int length = bc.getLength(); CharChunk cc = uri.getCharChunk(); cc.allocate(length, -1); String enc = connector.getURIEncoding(); //獲取URI解碼集 if (enc != null) { B2CConverter conv = request.getURIConverter(); try { if (conv == null) { conv = new B2CConverter(enc); request.setURIConverter(conv); } } catch (IOException e) {...} if (conv != null) { try { conv.convert(bc, cc, cc.getBuffer().length - cc.getEnd()); uri.setChars(cc.getBuffer(), cc.getStart(), cc.getLength()); return; } catch (IOException e) {...} } } // Default encoding: fast conversion byte[] bbuf = bc.getBuffer(); char[] cbuf = cc.getBuffer(); int start = bc.getStart(); for (int i = 0; i < length; i++) { cbuf[i] = (char) (bbuf[i + start] & 0xff); } uri.setChars(cbuf, 0, length); }

從上面的代碼可知,對URI的解碼操做是首先獲取Connector的解碼集,該配置在server.xml中

<Connector URIEncoding="utf-8" />

若是沒有定義則會採用默認編碼ISO-8859-1來解析。

對於Query String部分,咱們知道不管咱們是經過get方式仍是POST方式提交,全部的參數都是保存在Parameters,而後咱們經過 request.getParameter,解碼工做就是在第一次調用getParameter方法時進行的。在getParameter方法內部它調用 org.apache.catalina.connector.Request 的 parseParameters 方法,這個方法將會對傳遞的參數進行解碼。下面代碼只是parseParameters方法的一部分:

          //獲取編碼 String enc = getCharacterEncoding(); //獲取ContentType 中定義的 Charset boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI(); if (enc != null) { //若是設置編碼不爲空,則設置編碼爲enc parameters.setEncoding(enc); if (useBodyEncodingForURI) { //若是設置了Chartset,則設置queryString的解碼爲ChartSet parameters.setQueryStringEncoding(enc); } } else { //設置默認解碼方式 parameters.setEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); if (useBodyEncodingForURI) { parameters.setQueryStringEncoding(org.apache.coyote.Constants.DEFAULT_CHARACTER_ENCODING); } }

從上面代碼能夠看出對query String的解碼格式要麼採用設置的ChartSet要麼採用默認的解碼格式ISO-8859-1。注意這個設置的ChartSet是在 http Header中定義的ContentType,同時若是咱們須要改指定屬性生效,還須要進行以下配置:

<Connector URIEncoding="UTF-8" useBodyEncodingForURI="true"/>

上面部分詳細介紹了URL方式請求的編碼解碼過程。其實對於咱們而言,咱們更多的方式是經過表單的形式來提交。

表單GET

咱們知道經過URL方式提交數據是很容易產生亂碼問題的,因此咱們更加傾向於經過表單形式。當用戶點擊submit提交表單時,瀏覽器會更加設定的 編碼來編碼數據傳遞給服務器。經過GET方式提交的數據都是拼接在URL後面(能夠當作query String??)來提交的,因此tomcat服務器在進行解碼過程當中URIEncoding就起到做用了。tomcat服務器會根據設置的 URIEncoding來進行解碼,若是沒有設置則會使用默認的ISO-8859-1來解碼。假如咱們在頁面將編碼設置爲UTF-8,而 URIEncoding設置的不是或者沒有設置,那麼服務器進行解碼時就會產生亂碼。這個時候咱們通常能夠經過new String(request.getParameter(「name」).getBytes(「iso-8859-1″),」utf-8″) 的形式來獲取正確數據。

表單POST

 
對於POST方式,它採用的編碼也是由頁面來決定的即contentType。當我經過點擊頁面的submit按鈕來提交表單時,瀏覽器首先會根據 ontentType的charset編碼格式來對POST表單的參數進行編碼而後提交給服務器,在服務器端一樣也是用contentType中設置的字 符集來進行解碼(這裏與get方式就不一樣了),這就是經過POST表單提交的參數通常而言都不會出現亂碼問題。固然這個字符集編碼咱們是能夠本身設定 的:request.setCharacterEncoding(charset) 。
 
/////////////////////////////////////////////////////
JSP頁面編碼過程:

咱們知道JSP頁面是須要轉換爲servlet的,在轉換過程當中確定是要進行編碼的。在JSP轉換爲servlet過程當中下面一段代碼起到相當重要的做用。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="GBK" %>

在上面代碼中有兩個地方存在編碼:pageEncoding、contentType的charset。其中pageEncoding是jsp文件自己的編碼,而contentType的charset是指服務器發送給客戶端時的內容編碼。

在前面一篇博客中就提到過(java中文亂碼解決之道(四)—–java編碼轉換過程)jsp在轉換爲Servlet的過程當中是須要通過主要的三次編碼轉換過程(除去數據庫編碼轉換、頁面參數輸入編碼轉換):

第一次:轉換爲.java文件;

第二次:轉換爲.class文件;

第三次:業務邏輯處理後輸出。

第一階段

JVM將JSP編譯爲.jsp文件。在這個過程當中pageEncoding就起到做用了,JVM首先會獲取pageEncoding的值,若是該值存在則採用它設定的編碼來編譯,不然則採用file.encoding編碼來編譯。

第二階段

JVM將.java文件轉換爲.class文件。在這個過程就與任何編碼的設置都沒有關係了,無論JSP採用了什麼樣的編碼格式都將無效。通過這個階段後.jsp文件就轉換成了統一的Unicode格式的.class文件了。

第三階段

  1. 後臺通過業務邏輯處理後將產生的結果輸出到客戶端。在這個過程當中contentType的charset就發揮了功效。若是設置了charset則瀏覽器就會使用指定的編碼格式進行解碼,不然採用默認的ISO-8859-1編碼格式進行解碼處理。

流程如以下:

 
 
解決URL中文亂碼問題:

咱們主要經過兩種形式提交向服務器發送請求:URL、表單。而表單形式通常都不會出現亂碼問題,亂碼問題主要是在URL上面。經過前面幾篇博客的介 紹咱們知道URL向服務器發送請求編碼過程實在是實在太混亂了。不一樣的操做系統、不一樣的瀏覽器、不一樣的網頁字符集,將致使徹底不一樣的編碼結果。若是程序員 要把每一種結果都考慮進去,是否是太恐怖了?有沒有辦法,可以保證客戶端只用一種編碼方法向服務器發出請求?

 
有!這裏我主要提供如下幾種方法

1、javascript

使用javascript編碼不給瀏覽器插手的機會,編碼以後再向服務器發送請求,而後在服務器中解碼。在掌握該方法的時候,咱們須要料及javascript編碼的三個方法:escape()、encodeURI()、encodeURIComponent()。

escape

採用SIO Latin字符集對指定的字符串進行編碼。全部非ASCII字符都會被編碼爲%xx格式的字符串,其中xx表示該字符在字符集中所對應的16進制數字。例如,格式對應的編碼爲%20。它對應的解碼方法爲unescape()。

 
事實上escape()不能直接用於URL編碼,它的真正做用是返回一個字符的Unicode編碼值。好比上面「我是cm」的結果爲%u6211%u662Fcm,其中「我」對應的編碼爲6211,「是」的編碼爲662F,「cm」編碼爲cm。

注意,escape()不對」+」編碼。可是咱們知道,網頁在提交表單的時候,若是有空格,則會被轉化爲+字符。服務器處理數據的時候,會把+號處理成空格。因此,使用的時候要當心。

encodeURI

對整個URL進行編碼,它採用的是UTF-8格式輸出編碼後的字符串。不過encodeURI除了ASCII編碼外對於一些特殊的字符也不會進行編碼如:! @ # $& * ( ) = : / ; ? + ‘。

encodeURIComponent()

把URI字符串採用UTF-8編碼格式轉化成escape格式的字符串。相對於encodeURI,encodeURIComponent會更增強 大,它會對那些在encodeURI()中不被編碼的符號(; / ? : @ & = + $ , #)通通會被編碼。可是encodeURIComponent只會對URL的組成部分進行個別編碼,而不用於對整個URL進行編碼。對應解碼函數方法 decodeURIComponent。

固然咱們通常都是使用encodeURI方來進行編碼操做。所謂的javascript兩次編碼後臺兩次解碼就是使用該方法。javascript解決該問題有一次轉碼、兩次轉碼兩種解決方法。

一次轉碼

javascript轉碼:

var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(url); 

轉碼後的URL:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%E6%88%91%E6%98%AFcm

後臺處理:

String name = request.getParameter("name");
        System.out.println("前臺傳入參數:" + name); name = new String(name.getBytes("ISO-8859-1"),"UTF-8"); System.out.println("通過解碼後參數:" + name); 

輸出結果:

前臺傳入參數:??????cm
通過解碼後參數:我是cm

二次轉碼

javascript

var url = '<s:property value="webPath" />/ShowMoblieQRCode.servlet?name=我是cm'; window.location.href = encodeURI(encodeURI(url)); 

轉碼後的url:http://127.0.0.1:8080/perbank/ShowMoblieQRCode.servlet?name=%25E6%2588%2591%25E6%2598%25AFcm

後臺處理:

        String name = request.getParameter("name");
        System.out.println("前臺傳入參數:" + name); name = URLDecoder.decode(name,"UTF-8"); System.out.println("通過解碼後參數:" + name); 

輸出結果:

前臺傳入參數:E68891E698AFcm
通過解碼後參數:我是cm

filter

使用過濾器,過濾器LZ提供兩種,第一種設置編碼,第二種直接在過濾器中進行解碼操做。

過濾器1

該過濾器是直接設置request的編碼格式的。

public class CharacterEncoding implements Filter { private FilterConfig config ; String encoding = null; public void destroy() { config = null; } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { request.setCharacterEncoding(encoding); chain.doFilter(request, response); } public void init(FilterConfig config) throws ServletException { this.config = config; //獲取配置參數 String str = config.getInitParameter("encoding"); if(str!=null){ encoding = str; } } }

配置:

<!-- 中文過濾器的配置 --> <filter> <filter-name>chineseEncoding</filter-name> <filter-class>com.test.filter.CharacterEncoding</filter-class> <init-param> <param-name>encoding</param-name> <param-value>utf-8</param-value> </init-param> </filter> <filter-mapping> <filter-name>chineseEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

過濾器2

 
該過濾器在處理方法中將參數直接進行解碼操做,而後將解碼後的參數從新設置到request的attribute中。

 

public class CharacterEncoding implements Filter { protected FilterConfig filterConfig ; String encoding = null; public void destroy() { this.filterConfig = null; } /** * 初始化 */ public void init(FilterConfig filterConfig) { this.filterConfig = filterConfig; } /** * 將 inStr 轉爲 UTF-8 的編碼形式 * * @param inStr 輸入字符串 * @return UTF - 8 的編碼形式的字符串 * @throws UnsupportedEncodingException */ private String toUTF(String inStr) throws UnsupportedEncodingException { String outStr = ""; if (inStr != null) { outStr = new String(inStr.getBytes("iso-8859-1"), "UTF-8"); } return outStr; } /** * 中文亂碼過濾處理 */ public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) servletRequest; HttpServletResponse response = (HttpServletResponse) servletResponse; // 得到請求的方式 (1.post or 2.get), 根據不一樣請求方式進行不一樣處理 String method = request.getMethod(); // 1. 以 post 方式提交的請求 , 直接設置編碼爲 UTF-8 if (method.equalsIgnoreCase("post")) { try { request.setCharacterEncoding("UTF-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 2. 以 get 方式提交的請求 else { // 取出客戶提交的參數集 Enumeration<String> paramNames = request.getParameterNames(); // 遍歷參數集取出每一個參數的名稱及值 while (paramNames.hasMoreElements()) { String name = paramNames.nextElement(); // 取出參數名稱 String values[] = request.getParameterValues(name); // 根據參數名稱取出其值 // 若是參數值集不爲空 if (values != null) { // 遍歷參數值集 for (int i = 0; i < values.length; i++) { try { // 迴圈依次將每一個值調用 toUTF(values[i]) 方法轉換參數值的字元編碼 String vlustr = toUTF(values[i]); values[i] = vlustr; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } // 將該值以屬性的形式藏在 request request.setAttribute(name, values); } } } // 設置響應方式和支持中文的字元集 response.setContentType("text/html;charset=UTF-8"); // 繼續執行下一個 filter, 無一下個 filter 則執行請求 chain.doFilter(request, response); } }

配置:

<!-- 中文過濾器的配置 --> <filter> <filter-name>chineseEncoding</filter-name> <filter-class>com.test.filter.CharacterEncoding</filter-class> </filter> <filter-mapping> <filter-name>chineseEncoding</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>

其餘

一、設置pageEncoding、contentType

<%@ page language="java" contentType="text/html;charset=UTF-8" pageEncoding="UTF-8"%>

二、設置tomcat的URIEncoding

 
在默認狀況下,tomcat服務器使用的是ISO-8859-1編碼格式來編碼的,URIEncoding參數對get請求的URL進行編碼,因此 咱們只須要在tomcat的server.xml文件的<Connector>標籤中加上URIEncoding=」utf-8″便可。
相關文章
相關標籤/搜索