一、為什么要編碼?
由於人類的語言太多,因而表示這些語言的符號太多,無法用計算機的一個基本的存儲單元----byte來表示,因而必須要經過拆分或一些翻譯工作,才能讓計算機能理解。
byte一個字節即8個bit,所以能表示的字符范圍是0~255個,這滿足不了人類的需要,要解決這個矛盾必須需要一個新的數據結構char,從char到byte必須經過編碼。
二、常用編碼介紹
ASCII碼
總共128個,用一個字節的低7位表示,0~31是控制字符,如換行、回車、刪除等,32~126是打印字符,可以通過鍵盤輸入並且能夠顯示出來.
ISO-8859-1
擴展自ASCII,仍然是單字節編碼,一共能表示256個字符
GB2312
雙字節編碼。總編碼范圍A1~F7 ,其中A1~A9是符號區,包含682個符號,從B0~F7是漢字區,包含6763個漢字
GBK
擴展自GB2312,能表示21003個漢字,其編碼和GB2312是兼容的。GBK的文字編碼是用雙字節來表示的,即不論中、英文字符均使用雙字節來表示,為了區分中文,將其最高位都設定成1。GBK包含全部中文字符
GB18030
在實際應用系統中使用的並不廣泛
Unicode
Unicode 是 Java 和 XML 的基礎,使用0~65 535的雙字節無符號數對每一個字符進行編碼
UTF-8
是用以解決國際上字符的一種多字節編碼,它對英文使用8位(即一個字節),中文使用24為(三個字節)來編碼,使用Unicode編碼,一個英文字符要占用兩個字節,在Internet上,大多數的信息都是用英文來表示的,如果都采用Unicode編碼,將會使數據量增加一倍。為了減少存儲和傳輸英文字符數據的數據量,可以使用UTF-8編碼。
GBK、GB2312等與UTF8之間都必須通過Unicode編碼才能相互轉換:
GBK、GB2312--Unicode--UTF8
UTF8--Unicode--GBK、GB2312
三、對亂碼產生過程的分析
為了讓使用Java語言編寫的程序能在各種語言的平台下運行,Java在其內部使用Unicode字符集來表示字符,這樣就存在Unicode字符集和本地字符集進行轉換的過程。當在Java中讀取字符數據的時候,需要將本地字符集編碼的數據轉換為Unicode編碼,而在輸出字符數據的時候,則需要將Unicode編碼轉換為本地字符集編碼。
例如,在中文系統下,從控制台讀取一個字符“中”,實際上讀取的是“中”的GBK編碼0xD6D0,在Java語言中要將GBK編碼轉換為Unicode編碼0x4E2D,此時,在內存中,字符“中”對應的數值就是0x4E2D,當我們向控制台輸出字符時,Java語言將Unicode編碼再轉換為GBK編碼,輸出到控制台,中文系統再根據GBK字符集畫出相應的字符。
從上述過程來看,讀取和寫入的過程是可逆的,那么理應不會出現中文亂碼問題。然而,實際應用的情形,比上述過程要復雜得多。在Web應用中,通常都包括了瀏覽器、Web服務器、Web應用程序和數據庫等部分,每一部分都有可能使用不同的字符集,從而導致字符數據在各種不同的字符集之間轉換時,出現亂碼的問題。
在Java語言中,不同字符集編碼的轉換,都是通過Unicode編碼作為中介來完成的。例如,GBK編碼的字符“中”要轉換為ISO-8859-1編碼,其過程如下:
(1)因為在Java中的字符,都是用Unicode來表示的,所以GBK編碼的字符“中”要轉換為Unicode表示:0xD6D0->0x4E2D。
(2)將字符“中”的Unicode編碼轉換為ISO-8859-1編碼,因為Unicode編碼0x4E2D在ISO-8859-1中沒有對應的編碼,於是得到0x3f,也就是字符“?”。
下面的代碼演示了這一過程:
/GBK編碼的字符“中”轉換為Unicode編碼表示
String str="中";
//將字符“中”的Unicode編碼轉換為ISO-8859-1編碼
byte[] b=str.getBytes("ISO-8859-1");
for(int i=0;i<b.length;i++) {
//輸出轉換后的二進制代碼。
System.out.print(b[i]);
}
當從Unicode編碼向某個字符集轉換時,如果在該字符集中沒有對應的編碼,則得到0x3f(即問號字符?)。這就是為什么有時候我們輸入的是中文,在輸出時卻變成了問號。
從其他字符集向Unicode編碼轉換時,如果這個二進制數在該字符集中沒有標識任何的字符,則得到的結果是0xfffd。例如一個GBK的編碼值0x8140,從GB2312向Unicode轉換,然而由於0x8140不在GB2312字符集的編碼范圍(0xa1a1-0xfefe),當然也就沒有對應任何的字符,所以轉換后會得到0xfffd。
下面的代碼演示了這一過程。
// 構造一個二進制數據。
byte[] buf = { (byte) 0x81, (byte) 0x40, (byte) 0xb0, (byte) 0xa1 };
// 將二進制數據按照GB2312向Unicode編碼轉換。
String str = new String(buf, "GB2312");
for (int i = 0; i < str.length(); i++) {
// 取出字符串中的每個Unicode編碼的字符。
char ch = str.charAt(i);
// 將該字符對應的Unicode編碼以十六進制的形式輸出。
System.out.print(Integer.toHexString((int) ch));
System.out.print("--");
// 輸出該字符。
System.out.println(ch);
}
四、web開發避免中文亂碼
1.Jquery的get、 post方式提交中文亂碼
Ajax方式提交,如果參數中帶有中文參數,最好就是指定頁面格式的編碼.Jquery這里默認使用utf-8的編碼
值得注意的是: 在$.get()、$.post()方式中,要指定內容返回的內容的contentType格式.
get方法傳文字字符串就會有亂碼,因為是通過url傳參的。
所以你要在js客戶端經過2次轉碼,同樣服務器端也要轉碼。
$.get("AjaxService?userName=" + encodeURI(encodeURI(userName)), null, function (data) {
$("#result").html(data);
});
這2個效果是一樣的:encodeURIComponent(userName) 、 encodeURI(encodeURI(userName))
服務器端轉碼:String userName = URLDecoder.decode(request.getParameter("userName"), "UTF-8");
因為應用服務器會自動幫你做一次URLDecode,所以再加上你自己在代碼里面寫的URLDecode,一共就是兩個Decode了
一般情況下, 發送 encodeURIComponent(parmeName)+"="+encodeURIComponent(parmeValue);
接收時, 直接 String paramValue = request.getParameter(paramName); // 容器自動解碼.
我們知道 encodeURIComponent 使用的是 UTF-8 編碼規則來編的.
如果 request.getParameter(paramName) 時,容器也按 UTF-8 解的話,是正確的. 根本無須在客戶端
進行二次的 encodeURIComponent(...)
如果 request.getParameter(paramName),容器沒有按 UTF-8 解的話, 結果只有一個,就是亂碼!
容器按什么編碼來解碼,決定於 request.setCharacterEncoding(***) 或者 服務器程序配置.
如果你在 jsp 程序中,能夠 request.setCharacterEncoding("UTF-8"), 並且 修改服務器配置,讓容器在解 GET 提交的參數時,使用 UTF-8.
客戶端提交前不用二次編碼, 接收時,也只要直接 request.getParameter(paramName) 即可
Java、escape(str)和unescape(str);
對String對象進行編碼或者解碼,以便它們能在所有計算機上可讀;str中的非ASCII字符都是用【%xx】來表示的,其中xx表示該字符的16進制數,例如空格返回的是”%20”,字符值大於255的以%uxxxx格式存儲
注意:escape()方法不能夠對統一資源標識碼(URI)進行編碼,對其編碼應使用encodeURI()和encodeURIComponent()方法;
unescape()方法不能解碼URI,解碼需使用decodeURI()和decodeURIComponent();
這里建議參考Commons-lang3包中的StringEscapeUtils
2.JSP與頁面參數傳參亂碼
(1)頁面編碼不一致
<%@ page contentType="text/html; charset=gb2312"%> ... <meta http-equiv="Content-Type" content="text/html charset=gb2312"> ...
使用Servlet規范中的過慮器指定編碼,過濾器的在web.xml中的典型配置和主要代碼如下:
web.xml:
<filter> <filter-name>CharacterEncodingFilter</filter-name>
<filter-class>cn.com.tony.web.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>GBK</param-value> </init-param> </filter> <filter-mapping> <filter-name>CharacterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
CharacterEncodingFilter.java代碼段:
public class CharacterEncodingFilter implements Filter {
protected String encoding = null;
public void init(FilterConfig filterConfig) throws ServletException {
this.encoding = filterConfig.getInitParameter("encoding");
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
request.setCharacterEncoding(encoding);
response.setContentType("text/html;charset="+encoding);
chain.doFilter(request, response);
}
}
或者使用框架提供的亂碼過濾器如Spring
<filter>
<filter-name>EncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>GBK</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>EncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
3.鏈接傳參亂碼
在傳參的jsp對中文進行編碼:href="new.jsp?name=java.net.URLEncoder.encode("鏈接")";
在接受的jsp對中文進行轉碼:String str = URLDecoder.decode(request.getParameter("name "), "utf-8");
讀取的編碼格式要跟頁面中設置的編碼格式一致。
4.網頁編碼格式設置:
(1)、指定文件的存儲編碼,很明顯,該設置應該置於文件的開頭。例如:<%@page pageEncoding="GBK"%>,正常顯示中文,如果不設置默認是iso8859-1,它是不支持中文的。
(2)、jsp輸出,即:browser顯示網頁的時候,首先使用response.setCharacterEncoding()中指定的編碼,也可以是<%@ page contentType="text/html; charset= GBK" %>。如果未指定,則會使用網頁中meta項指定中的contentType。
(3)、 meta設置
指定網頁使用的編碼,該設置對靜態網頁尤其有作用。因為靜態網頁無法采用jsp的設置,而且也無法執行response.setCharacterEncoding()。例如:<META http-equiv="Content-Type" content="text/html; charset=GBK" />而在jsp中meta的優先級最低,沒有以上兩種的編碼時才采用這中編碼推薦只用utf-8,它支持所有字符。
若是Servlet顯示網頁就用response.setContentType("text/html;charset=utf-8");
5.數據庫讀取亂碼
大部分數據庫都支持以unicode編碼方式,所以解決Java與數據庫之間的亂碼問題比較明智的方式是直接使用unicode編碼與數據庫交互。 很多數據庫驅動自動支持unicode,其他大部分數據庫驅動,可以在驅動的url參數中指定,如 mysql驅動:jdbc:mysql://localhost/MYTEST?useUnicode=true&characterEncoding=GBK。
6.瀏覽器編碼異常
亂碼問題
<%@ page language="java" contentType="text/html; charset=GBK" pageEncoding="GBK"%>
......
<%
//*****寫在首行*****//
request.setCharacterEncoding("UTF-8");
...
%>
在瀏覽器中,中文通過url傳遞都會默認被編碼,所以在url中的中文還必須編碼后再使用,
使用 <form>...</form> 提交的,瀏覽器才會編碼.
<form>提交時,瀏覽器使用什么編碼編,決定於 form 的 accept-charset 屬性(標准瀏覽器) 或者 document.charset(IE)
<form accept-charset="...." >...</form>
提交 application/x-form-www-encoded 表單時,瀏覽器 把所有參數 按 key=value 的形式組合
分別對 key, value 進行編碼.
多個參數間,用 "&" 連接, 如: key2=%E4%B8%AD%E6%96%87&key=AAAA&key=BBB&key1=CCC&%E6%B1%89%E5%AD%97=%E4%B8%AD%E6%96%87
ajax 提交的,需要自己手動設置編碼
瀏覽器url編碼
eg : "2014中華小當家"先編碼成utf-8,再在url地址欄中傳遞,ie10地址欄輸入中文不會亂碼,火狐和chrome會,
7.通過參數設置接口編碼
WebUtils.java
public static String getQueryValue(String queryString,String key){
if(queryString==null || queryString.length()==0)return null;
int reqIdx = queryString.indexOf(key);//req_enc=utf-8&resp_enc=gbk
String enc = null;
if(reqIdx!=-1){
reqIdx = reqIdx+key.length();
int endIdx = reqIdx;
for (;
endIdx < queryString.length() && queryString.charAt(endIdx)!='&';
endIdx++) {
}
if(endIdx>reqIdx){
enc = queryString.substring(reqIdx,endIdx);
if(!enc.equalsIgnoreCase("gbk")
&& !enc.equalsIgnoreCase("utf-8")){
enc = null;
}else{
System.err.println("error :"+key+" is "+ enc);
}
}
}
return enc;
}
setCharset.jsp
<%@ page contentType="text/html;charset=GBK" %><%
String queryString = request.getQueryString();
String reqEnc = getQueryValue(queryString,"req_enc=");
if(reqEnc!=null){
request.setCharacterEncoding(reqEnc);
}
String respEnc = getQueryValue(queryString,"resp_enc=");
if(respEnc!=null){
response.setCharacterEncoding(respEnc);
}
%>
五、總結
上面提到的方法應該能解決大部分亂碼問題,如果在其他地方還出現亂碼,可能需要手動修改代碼。解決Java亂碼問題的關鍵在於在字節與字符的轉換 過程中,你必須知道原來字節或轉換后的字節的編碼方式,轉換時采用的編碼必須與這個編碼方式保持一致,否則將會出現Java文亂碼。
參考:

