亂碼問題


  路邊逮一美國IT佬,問其亂碼問題,愕然相對,你這是鬧哪樣,全然不知。相反,如遇國內IT民工,如臨大敵。誠然,在國內做軟件,像我這種初級程序員,遇到字符亂碼,往往會不知所措,直冒汗。第一台計算機誕生不久,就有了ASCII編碼,后來因ASCII不能滿足現下的字符,就由ISO組織擴展成為ISO-8859-1。計算機的普及,各個國家都有了自己的編碼,目的可以在計算機上可以顯示它們的語言。比如GBK編碼來表示中文。但這也產生了編碼不一致的問題,后來unicode統一了全世界的語言的編碼規則,它可以表示全世界的語言。那為何美國人就不會遇到字符亂碼的問題?美國人使用的是英文,而中國人使用的是中文。原因是全世界的字符編碼對英文的編碼規則是一致的,都是以一個字節來保存英文的。而中文不同,有些編碼根本不支持中文,比如ISO-8859-1,有些編碼對中文的編碼規則不一致,比如GBK以2個字節,而UTF-8是以3個字節保存中文。

一、為什么需要字符編碼

  了解此問題前,首先得理解幾個過程

  • 編碼過程:將非二進制字符轉換成二進制("string".getBytes(String encoding))
  • 解碼過程:將二進制轉換為字符(new String(byte[] c,String encoding))
  • 存儲過程:計算機是一個字節一個字節存儲的,比如"中文"通過GBK編碼后為d6d0 cec4,然后計算機拆分為d6 d0 ce c4以一個字節一個字節將信息存儲

計算機只識別二進制即1010,由1和0組成的序列集,當需計算機識別其他字符時,就必須將其轉換成二進制存儲到計算機中。當需從計算機中讀取信息以某種字符形式表示時,就需從中讀取二進制信息,然后以特定的字符編碼將二進制轉換為字符。字符編碼指將字符轉換為二進制的規則。比較常見的字符編碼ISO-8859-1(常用於網絡傳輸),GBK,UTF-8(unicode的一種實現)。

二、為什么會出現亂碼

  在我們溝通過程中,經常也會出現"亂碼問題"。小明想表達的意思是A,說出來的意思是B,小芳接收到的意思是C,小芳理解的意思是D。A=D時,證明此溝通成功。但往往溝通過程中,沒那么順暢,出現A!=B,B!=C,C!=D,其中任何一個環節出錯,都會造成A!=D的情況。A是二進制信息,B是編碼后的字符,C是通過某種途徑傳輸B后的字符,D是

解碼后的字符。傳輸過程中如果沒有信息丟失,B=C。所以問題往往會出現A!=B和C!=D的情況,這兩種情況就是編碼和解碼不一致導致的,這也是產生亂碼的根本原因。

三、亂碼問題的情景

  細心的童靴會留意到前面溝通B->C過程中,是要將表達意思傳遞給小芳。當系統需要從外部資源讀寫數據時,外部資源可以是文件(數據庫)、網絡及內存。這里有兩個過程,數據傳輸過程接收端發送端編碼和解碼的過程。因為發送端需要將數據編碼成二進制,由計算機通過某種載體傳輸到接收端,接收端接收到二進制數據,就需要將數據解碼。當出現亂碼問題時,我們首先確定2個端的字符編碼方式,然后統一2個端的編碼方式即可。亂碼問題的情景有二種,A!=B和B!=D(假設B=C),A!=B是編譯階段,B!=D是系統從外部資源讀寫數據階段。總結一下亂碼問題的情景:

  • Java 編譯階段(A!=B)
    • Java文件
    • JSP文件
  • 從外部資源讀寫數據階段(B!=D)
    • WEB交互
      • 表單提交get/post
      • 超鏈接
      • XMLHttpRequest異步提交get/post
      • 直接在瀏覽器輸入URL
    • 數據庫
    • 文件
      • 顯式的操作文件,I/O流
      • 編寫代碼階段

四、解決亂碼問題

  • 文件

  編寫代碼階段,eclipse平台上編寫完代碼后,需要保存到文件,普通的Java文件,通常會根據當前操作系統的默認字符編碼來保存Java文件,比如在中文環境下通常是GBK。而在jsp中,由<%@page pageEncoding="gbk"%>pageEncoding來指定頁面的字符編碼。

  在使用I/O流操作文件時,有字節流和字符流2種方式。當使用字節流時固然是沒有問題的,但當使用字符流時,請看下面源碼。

 1 public class FromOutsideData {
 2     private final static String FILE_SEPARATOR= File.separator;
 3     
 4     public static void main(String[] args) throws IOException {
 5         String path = "D:" + FILE_SEPARATOR + "1.txt";
 6         File file = new File(path);
 7         write(file, "utf-8");
 8         read(file); // 如果去讀以utf-8編碼后的文件,就會出現亂碼。
 9         read(file, "utf-8"); // 指定utf-8去讀取文件,正常。
10     }
11     
12     public static void read(File file) throws IOException {
13         // 使用字符流的原理是先使用字節流每次讀2個字節,然后根據當前操作系統的默認字符編碼來解碼成字符
14         BufferedReader br = new BufferedReader(new FileReader(file));
15         String line = null;
16         StringBuilder sb = new StringBuilder();
17         while ((line = br.readLine()) != null) {
18             sb.append(line);
19         }
20         System.out.println(sb.toString());
21     }
22     
23     public static void read(File file, String charset) throws IOException {
24         // 使用這種方式可以顯式的指定字符編碼來解碼
25         BufferedReader br = new BufferedReader(new InputStreamReader(new FileInputStream(file), charset));
26         String line = null;
27         StringBuilder sb = new StringBuilder();
28         while ((line = br.readLine()) != null) {
29             sb.append(line);
30         }
31         System.out.println(sb.toString());
32     }
33     
34     /**
35      * 根據charset的字符編碼寫文件
36      */
37     public static void write(File file, String charset) throws IOException {
38         BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file, true), charset));
39         bw.write("is 最牛x轟轟滴");
40         bw.flush();
41         bw.close();
42     }
43 }
View Code
  • WEB交互

                

WEB交互,這里特指瀏覽器與服務器基於http協議進行數據傳輸的過程。http報頭的首部contentType指定了數據傳輸過程中的字符編碼和內容編碼。字符編碼不等同於內容編碼,像MIME指的是內容編碼,它規定了內容的格式。像text/html、application/x-www-form-urlencoded、application/vnd ms-excel等等,例如contentType:"text/html;charset=utf-8",指瀏覽器告訴服務器,傳遞的數據的內容格式是html,字符編碼是utf-8。同樣服務器返回的數據,也是通過contentType來告訴瀏覽器數據的內容編碼和字符編碼。比如,servlet可以通過response.setContentType("text/html;charset=utf-8");所以我們只要正確的指定contentType的字符編碼就可以避免WEB交互的亂碼問題。在jsp頁面有2個部分可以指定contentType的,第一是<%@page contentType="text/html;charset=utf-8"%>,第二是<meta http-equiv="Content-Type" content="text/html; charset=utf-8">,但前者的優先級高於后者,瀏覽器確定頁面字符編碼有4個步驟,首先看<%@page%>有沒有指定,然后是自動檢測,而后會meta,最后會按ISO-8859-1默認編碼來編碼。服務器設置字符解碼的地方有兩個部分:第一是WEB服務器的配置文件指定,第二是request.setCharacterEncoding("utf-8");知道了這些后,我們再來看看WEB交互的亂碼問題。

瀏覽器發送數據到服務器的字符編碼

  1. 超鏈接
foo://example.com:8042/over/there?name=ferret#nose
    \_/ \______________/ \________/\_________/ \__/
     |         |              |         |        |
scheme     authority        path     query   fragment    前面是URL的組成成分,path部分的編碼會比較麻煩,這部分會由瀏覽器語言版本決定,如果是中文則以GBK編碼,如果英文環境則以ISO-5589-1編碼。所以我們需要用js的方法encodeURIComponent(s,enc)來統一編碼path部分,而query部分由contentType決定的。
  2.   直接在瀏覽器輸入URL(由瀏覽器的語言版本決定)
  3.   表單提交get/post(由contentType決定)
  4.   XMLHttpRequest異步提交get/post(不由任何決定,一定是utf-8編碼)

服務器對數據解碼的字符編碼
在1,2和3中的get提交產生的都是get請求,實質就是拼接url。服務器對其設置對url解碼的字符編碼, 對於tomcat服務器,該文件是server.xml
<Connector port="8080" protocol="HTTP/1.1" 
maxThreads="150" connectionTimeout="20000" 
redirectPort="8443" URIEncoding="GBK"/>
URIEncoding告訴服務器servlet解碼URL時采用的編碼。而resin服務器可以直接通過request.setCharacterEncoding("gbk")指定;
3.form表單post請求由request.setContentType("gbk")來指定編碼;
4.如果請求中指定了contentType:'application/x-www-form-urlencoded;charset=utf-8'(注意這里是分號,不是逗號,如果是逗號,在firefox會報500),服務器會優先按charset指定的編碼來解碼,然后是request.setCharacterEncoding("utf-8");那為什么在firefox不加contentType報頭也不會亂碼呢,因為它會自動加上這個頭。而ie和chrome不會。

服務器返回數據給瀏覽器的字符編碼
服務器也是通過指定contentType響應頭來告訴瀏覽器,比如,servlet的response.setCharacterEncoding("GBK");如果服務器沒有指定contentType,如果返回的是html,那么瀏覽器首先會自己探測,如果遇到meta有contentType報頭就會以其指定的編碼解析。如果返回的是json數據就必須指定,不然如果默認的字符編碼,比如ISO-8859-1,不支持中文的話,就會出現亂碼問題。
  • Java 編譯階段

當我們編寫完源碼后,通常會運行javac xxx, 將其編譯為*.class文件,編譯的時候會讀取源碼,那么是以什么編碼來讀取呢,默認是按操作系統的語言環境的,中文環境默認是gbk,如我們需要指定,可以 javac xxx -encoding utf-8。

  • 數據庫
數據庫設置編碼會有2個過程,第一,當我們 應用連接數據庫時,告訴數據庫用什么字符編碼來解碼。第二,數據庫 以什么字符編碼來保存數據。前者可使用 jdbc:mysql://localhost:3306/db? useUnicode=true&characterEncoding=UTF-8;后者由數據庫決定,可到數據庫去設置。
五、總結
  現存的字符編碼環境對中文的支持是各不相同的,如果端與端之間采用了不同的字符編碼,就會出現亂碼問題。同時介紹了亂碼問題出現的情景及其解決方案。由於個人文字功底略差,很多想表達的東西都未能表達。在文章的整體措詞方面略顯粗糙,歡迎拍磚。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM