Java中文亂碼處理

java編碼轉換過程

咱們老是用一個java類文件和用戶進行最直接的交互(輸入、輸出),這些交互內容包含的文字可能會包含中文。不管這些java類是與數據庫交互,仍是與前端頁面交互,他們的生命週期老是這樣的:javascript

一、程序員在操做系統上經過編輯器編寫程序代碼而且以.java的格式保存操做系統中,這些文件咱們稱之爲源文件。html

二、經過JDK中的javac.exe編譯這些源文件造成.class類。前端

三、直接運行這些類或者部署在WEB容器中運行,獲得輸出結果。java

這些過程是從宏觀上面來觀察的,瞭解這個確定是不行的,咱們須要真正來了解java是如何來編碼和被解碼的:mysql

第一步:當咱們用編輯器編寫java源文件,程序文件在保存時會採用操做系統默認的編碼格式(通常咱們中文的操做系統採用的是GBK編碼格式)造成一個.java文件。java源文件是採用操做系統默認支持的file.encoding編碼格式保存的。下面代碼能夠查看系統的file.encoding參數值。程序員

System.out.println(System.getProperty("file.encoding"));  

第二步:當咱們使用javac.exe編譯咱們的java文件時,JDK首先會確認它的編譯參數encoding來肯定源代碼字符集,若是咱們不指定該編譯參數,JDK首先會獲取操做系統默認的file.encoding參數,而後JDK就會把咱們編寫的java源程序從file.encoding編碼格式轉化爲JAVA內部默認的UNICODE格式放入內存中。web

第三步:JDK將上面編譯好的且保存在內存中信息寫入class文件中,造成.class文件。此時.class文件是Unicode編碼的,也就是說咱們常見的.class文件中的內容不管是中文字符仍是英文字符,他們都已經轉換爲Unicode編碼格式了。sql

在這一步中對對JSP源文件的處理方式有點兒不一樣:WEB容器調用JSP編譯器,JSP編譯器首先會查看JSP文件是否設置了文件編碼格式,若是沒有設置則JSP編譯器會調用調用JDK採用默認的編碼方式將JSP文件轉化爲臨時的servlet類,而後再編譯爲.class文件並保持到臨時文件夾中。chrome

第四步:運行編譯的類:在這裏會存在一下幾種狀況數據庫

一、直接在console上運行。

二、JSP/Servlet類。

三、java類與數據庫之間。

這三種狀況每種狀況的方式都會不一樣,

1.Console上運行的類

這種狀況下,JVM首先會把保存在操做系統中的class文件讀入到內存中,這個時候內存中class文件編碼格式爲Unicode,而後JVM運行它。若是須要用戶輸入信息,則會採用file.encoding編碼格式對用戶輸入的信息進行編碼同時轉換爲Unicode編碼格式保存到內存中。程序運行後,將產生的結果再轉化爲file.encoding格式返回給操做系統並輸出到界面去。整個流程以下:

201412260001

在上面整個流程中,凡是涉及的編碼轉換都不能出現錯誤,不然將會產生亂碼。

2.Servlet類

因爲JSP文件最終也會轉換爲servlet文件(只不過存儲的位置不一樣而已),因此這裏咱們也將JSP文件歸入其中。

當用戶請求Servlet時,WEB容器會調用它的JVM來運行Servlet。首先JVM會把servlet的class加載到內存中去,內存中的servlet代碼是Unicode編碼格式的。而後JVM在內存中運行該Servlet,在運行過程當中若是須要接受從客戶端傳遞過來的數據(如表單和URL傳遞的數據),則WEB容器會接受傳入的數據,在接收過程當中若是程序設定了傳入參數的的編碼則採用設定的編碼格式,若是沒有設置則採用默認的ISO-8859-1編碼格式,接收的數據後JVM會將這些數據進行編碼格式轉換爲Unicode而且存入到內存中。運行Servlet後產生輸出結果,同時這些輸出結果的編碼格式仍然爲Unicode。緊接着WEB容器會將產生的Unicode編碼格式的字符串直接發送置客戶端,若是程序指定了輸出時的編碼格式,則按照指定的編碼格式輸出到瀏覽器,不然採用默認的ISO-8859-1編碼格式。整個過程流程圖以下:

2014122700001

3.數據庫部分

咱們知道java程序與數據庫的鏈接都是經過JDBC驅動程序來鏈接的,而JDBC驅動程序默認的是ISO-8859-1編碼格式的,也就是說咱們經過java程序向數據庫傳遞數據時,JDBC首先會將Unicode編碼格式的數據轉換爲ISO-8859-1的編碼格式,而後在存儲在數據庫中,即在數據庫保存數據時,默認格式爲ISO-8859-1。

201412270001

編碼&解碼

在上篇博客中LZ闡述了三個渠道的編碼轉換過程,下面LZ將結束java在那些場合須要進行編碼和解碼操做,並詳序中間的過程,進一步掌握java的編碼和解碼過程。在java中主要有四個場景須要進行編碼解碼操做:

1:I/O操做

2:內存

3:數據庫

4:javaWeb

下面主要介紹前面兩種場景,數據庫部分只要設置正確編碼格式就不會有什麼問題,javaWeb場景過多須要瞭解URL、get、POST的編碼,servlet的解碼,因此javaWeb場景下節LZ介紹。

I/O操做

在前面LZ就提過亂碼問題無非就是轉碼過程當中編碼格式的不統一產生的,好比編碼時採用UTF-8,解碼採用GBK,但最根本的緣由是字符到字節或者字節到字符的轉換出問題了,而這中狀況的轉換最主要的場景就是I/O操做的時候。固然I/O操做主要包括網絡I/O(也就是javaWeb)和磁盤I/O。網絡I/O下節介紹。

首先咱們先看I/O的編碼操做。

201412300001

InputStream爲字節輸入流的全部類的超類,Reader爲讀取字符流的抽象類。java讀取文件的方式分爲按字節流讀取和按字符流讀取,其中InputStream、Reader是這兩種讀取方式的超類。

按字節

咱們通常都是使用InputStream.read()方法在數據流中讀取字節(read()每次都只讀取一個字節,效率很是慢,咱們通常都是使用read(byte[])),而後保存在一個byte[]數組中,最後轉換爲String。在咱們讀取文件時,讀取字節的編碼取決於文件所使用的編碼格式,而在轉換爲String過程當中也會涉及到編碼的問題,若是二者之間的編碼格式不一樣可能會出現問題。例如存在一個問題test.txt編碼格式爲UTF-8,那麼經過字節流讀取文件時所得到的數據流編碼格式就是UTF-8,而咱們在轉化成String過程當中若是不指定編碼格式,則默認使用系統編碼格式(GBK)來解碼操做,因爲二者編碼格式不一致,那麼在構造String過程確定會產生亂碼,以下:

File file = new File("C:\\test.txt");  
InputStream input = new FileInputStream(file);  
StringBuffer buffer = new StringBuffer();  
byte[] bytes = new byte[1024];  
for(int n ; (n = input.read(bytes))!=-1 ; ){  
    buffer.append(new String(bytes,0,n));  
}  
System.out.println(buffer); 

輸出結果:鍩挎垜鏄?cm

test.txt中的內容爲:我是 cm。

要想不出現亂碼,在構造String過程當中指定編碼格式,使得編碼解碼時二者編碼格式保持一致便可:

buffer.append(new String(bytes,0,n,"UTF-8")); 

按字符

其實字符流能夠看作是一種包裝流,它的底層仍是採用字節流來讀取字節,而後它使用指定的編碼方式將讀取字節解碼爲字符。在java中Reader是讀取字符流的超類。因此從底層上來看按字節讀取文件和按字符讀取沒什麼區別。在讀取的時候字符讀取每次是讀取留個字節,字節流每次讀取一個字節。

字節&字符轉換

字節轉換爲字符必定少不了InputStreamReader。API解釋以下:InputStreamReader 是字節流通向字符流的橋樑:它使用指定的 charset 讀取字節並將其解碼爲字符。它使用的字符集能夠由名稱指定或顯式給定,或者能夠接受平臺默認的字符集。 每次調用 InputStreamReader 中的一個 read() 方法都會致使從底層輸入流讀取一個或多個字節。要啓用從字節到字符的有效轉換,能夠提早從底層流讀取更多的字節,使其超過知足當前讀取操做所需的字節。API解釋很是清楚,InputStreamReader在底層讀取文件時仍然採用字節讀取,讀取字節後它須要根據一個指定的編碼格式來解析爲字符,若是沒有指定編碼格式則採用系統默認編碼格式。

String file = "C:\\test.txt";   
         String charset = "UTF-8";   
         // 寫字符換轉成字節流  
         FileOutputStream outputStream = new FileOutputStream(file);   
         OutputStreamWriter writer = new OutputStreamWriter(outputStream, charset);   
         try {   
            writer.write("我是 cm");   
         } finally {   
            writer.close();   
         }   
           
         // 讀取字節轉換成字符  
         FileInputStream inputStream = new FileInputStream(file);   
         InputStreamReader reader = new InputStreamReader(   
         inputStream, charset);   
         StringBuffer buffer = new StringBuffer();   
         char[] buf = new char[64];   
         int count = 0;   
         try {   
            while ((count = reader.read(buf)) != -1) {   
                buffer.append(buf, 0, count);   
            }   
         } finally {   
            reader.close();   
         }  
         System.out.println(buffer);  

內存

首先咱們看下面這段簡單的代碼

String s = "我是 cm";   
byte[] bytes = s.getBytes();   
String s1 = new String(bytes,"GBK");   
String s2 = new String(bytes);

在這段代碼中咱們看到了三處編碼轉換過程(一次編碼,兩次解碼)。先看String.getTytes():

public byte[] getBytes() {  
        return StringCoding.encode(value, 0, value.length);  
    }  

內部調用StringCoding.encode()方法操做:

static byte[] encode(char[] ca, int off, int len) {  
        String csn = Charset.defaultCharset().name();  
        try {  
            // use charset name encode() variant which provides caching.  
            return encode(csn, ca, off, len);  
        } catch (UnsupportedEncodingException x) {  
            warnUnsupportedCharset(csn);  
        }  
        try {  
            return encode("ISO-8859-1", ca, off, len);  
        } catch (UnsupportedEncodingException x) {  
            // If this code is hit during VM initialization, MessageUtils is  
            // the only way we will be able to get any kind of error message.  
            MessageUtils.err("ISO-8859-1 charset not available: "  
                             + x.toString());  
            // If we can not find ISO-8859-1 (a required encoding) then things  
            // are seriously wrong with the installation.  
            System.exit(1);  
            return null;  
        }  
    }  

encode(char[] paramArrayOfChar, int paramInt1, int paramInt2)方法首先調用系統的默認編碼格式,若是沒有指定編碼格式則默認使用ISO-8859-1編碼格式進行編碼操做,進一步深刻以下:

String csn = (charsetName == null) ? "ISO-8859-1" : charsetName; 

一樣的方法能夠看到new String 的構造函數內部是調用StringCoding.decode()方法:

public String(byte bytes[], int offset, int length, Charset charset) {  
        if (charset == null)  
            throw new NullPointerException("charset");  
        checkBounds(bytes, offset, length);  
        this.value =  StringCoding.decode(charset, bytes, offset, length);  
    }  

decode方法和encode對編碼格式的處理是同樣的。

對於以上兩種狀況咱們只須要設置統一的編碼格式通常都不會產生亂碼問題。

編碼&編碼格式

首先先看看java編碼類圖[1]

201412300003

首先根據指定的chart設置ChartSet類,而後根據ChartSet建立ChartSetEncoder對象,最後再調用 CharsetEncoder.encode 對字符串進行編碼,不一樣的編碼類型都會對應到一個類中,實際的編碼過程是在這些類中完成的。下面時序圖展現詳細的編碼過程:

201412300002

經過這編碼的類圖和時序圖能夠了解編碼的詳細過程。下面將經過一段簡單的代碼對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

圖以下:

201412310001

編碼&解碼

經過下圖咱們能夠了解在javaWeb中有哪些地方有轉碼:

201501060001

用戶想服務器發送一個HTTP請求,須要編碼的地方有url、cookie、parameter,通過編碼後服務器接受HTTP請求,解析HTTP請求,而後對url、cookie、parameter進行解碼。在服務器進行業務邏輯處理過程當中可能須要讀取數據庫、本地文件或者網絡中的其餘文件等等,這些過程都須要進行編碼解碼。當處理完成後,服務器將數據進行編碼後發送給客戶端,瀏覽器通過解碼後顯示給用戶。在這個整個過程當中涉及的編碼解碼的地方較多,其中最容易出現亂碼的位置就在於服務器與客戶端進行交互的過程。

上面整個過程能夠歸納成這樣,頁面編碼數據傳遞給服務器,服務器對得到的數據進行解碼操做,通過一番業務邏輯處理後將最終結果編碼處理後傳遞給客戶端,客戶端解碼展現給用戶。因此下面我就請求對javaweb的編碼&解碼進行闡述。

請求

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

一、URL方式直接訪問。

二、頁面連接。

三、表單get提交

四、表單post提交

URL方式

對於URL,若是該URL中所有都是英文的那卻是沒有什麼問題,若是有中文就要涉及到編碼了。如何編碼?根據什麼規則來編碼?又如何來解碼呢?下面LZ將一一解答!首先看URL的組成部分:

201501060002

在這URL中瀏覽器將會對path和parameter進行編碼操做。爲了更好地解釋編碼過程,使用以下URL

http://127.0.0.1:8080/perbank/我是cm?name=我是cm

將以上地址輸入到瀏覽器URL輸入框中,經過查看http 報文頭信息咱們能夠看到瀏覽器是如何進行編碼的。下面是IE、Firefox、Chrome三個瀏覽器的編碼狀況:

201501080001

201501080002

201501080003

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

 

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頁面是須要轉換爲servlet的,在轉換過程當中確定是要進行編碼的。在JSP轉換爲servlet過程當中下面一段代碼起到相當重要的做用。

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

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

jsp在轉換爲Servlet的過程當中是須要通過主要的三次編碼轉換過程(除去數據庫編碼轉換、頁面參數輸入編碼轉換):

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

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

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

第一階段

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

第二階段

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

第三階段

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

流程如以下:

201501150001

 


 

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

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

1、javascript

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

escape

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

201501150002

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

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

encodeURI

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

201501150003

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

二次轉碼

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"便可。


-----原文出自:http://cmsblogs.com/?p=1526,請尊重做者辛勤勞動成果,轉載說明出處.

-----我的站點:http://cmsblogs.com

相關文章
相關標籤/搜索