歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux編程 >> Linux編程 >> Java Web中的中文編碼

Java Web中的中文編碼

日期:2017/3/1 9:11:29   编辑:Linux編程

  Java Web開發中經常會遇到中文編碼問題,那麼為什麼需要編碼呢?因為人類需要表示的符號太多,無法用1個字節來表示,而計算機中存儲信息最小單元為1個字節。所以必須指定char與byte之間的編碼規則了。

1 常見的編碼方式

  計算機中提供了多種編碼方式,常見的有ASCII、ISO-8859-1、GBK、GB2312、UTF-16、UTF-8等。

  • ASCII 碼
    • 學過計算機的人都知道 ASCII 碼,總共有 128 個,用一個字節的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入並且能夠顯示出來。
  • ISO-8859-1
    • 128 個字符顯然是不夠用的,於是 ISO 組織在 ASCII 碼基礎上又制定了一些列標准用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數西歐語言字符,所有應用的最廣泛。ISO-8859-1 仍然是單字節編碼,它總共能表示 256 個字符。
  • GB2312
    • 它的全稱是《信息交換用漢字編碼字符集 基本集》,它是雙字節編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字。
  • GBK
    • 全稱叫《漢字內碼擴展規范》,是國家技術監督局為 windows95 所制定的新的漢字內碼規范,它的出現是為了擴展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,並且不會有亂碼。
  • GB18030
    • 全稱是《信息交換用漢字編碼字符集》,是我國的強制標准,它可能是單字節、雙字節或者四字節編碼,它的編碼與 GB2312 編碼兼容,這個雖然是國家標准,但是實際應用系統中使用的並不廣泛。
  • UTF-16
    • 說到 UTF 必須要提到 Unicode(Universal Code 統一碼),ISO 試圖想創建一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯。可想而知這個字典是多麼的復雜,關於 Unicode 的詳細規范可以參考相應文檔。Unicode 是 Java 和 XML 的基礎,下面詳細介紹 Unicode 在計算機中的存儲形式。
    • UTF-16 具體定義了 Unicode 字符在計算機中存取方法。UTF-16 用兩個字節來表示 Unicode 轉化格式,這個是定長的表示方法,不論什麼字符都可以用兩個字節表示,兩個字節是 16 個 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每兩個字節表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為內存的字符存儲格式的一個很重要的原因。
  • UTF-8
    • UTF-16 統一采用兩個字節表示一個字符,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字符用一個字節就可以表示的現在要兩個字節表示,存儲空間放大了一倍,在現在的網絡帶寬還非常有限的今天,這樣會增大網絡傳輸的流量,而且也沒必要。而 UTF-8 采用了一種變長技術,每個編碼區域有不同的字碼長度。不同類型的字符可以是由 1~6 個字節組成。

2 編碼的場景

  在IO操作中我們一般需要進行編碼,這裡的IO操作包括磁盤IO、網絡IO等,比如下面就是一個磁盤IO的例子:

public static void main(String[] args) throws IOException {
        String file    = "H:/name.txt";
        String charset = "utf-8";

        // 寫字符到字節流
        FileOutputStream   outputStream = new FileOutputStream(file);
        OutputStreamWriter writer       = new OutputStreamWriter(outputStream, charset);

        try {
                writer.write("中文名杭州");
        }
        finally {
                writer.close();
        }

        // 讀字節流到字符
        FileInputStream   inputStream = new FileInputStream(file);
        InputStreamReader reader      = new InputStreamReader(inputStream, charset);

        try {
                char[] buffer = new char[64];
                reader.read(buffer);
                System.out.println(buffer);
        }
        finally {
                reader.close();
        }
}

  在程序中設計IO編解碼,只要我們指定統一的編解碼Charset字符集,一般不會有問題(或者編解碼都是在同一個系統中,都使用默認的字符集)。但是如果我們指定的編解碼不一致,就會出現中文亂碼問題。如果把代碼中的InputStreamReader指定為GBK編碼,就會出現亂碼問題。

InputStreamReader reader      = new InputStreamReader(inputStream, "GBK");

  Java中使用String類型也是可以指定字節到字符轉換的編碼字符集的。

1 String xxx = "你好";
2 String yyy = new String(xxx.getBytes(), "utf-8");
3 System.out.println(yyy);

3 幾種編碼格式比較

  對中文字符GBK/GB2312/UTF-16/UTF-8四種編碼格式都能處理,GB2312 與 GBK 編碼規則類似,但是 GBK 范圍更大,它能處理所有漢字字符,所以 GB2312 與 GBK 比較應該選擇 GBK。UTF-16 與 UTF-8 都是處理 Unicode 編碼,它們的編碼規則不太相同,相對來說 UTF-16 編碼效率最高,字符到字節相互轉換更簡單,進行字符串操作也更好。它適合在本地磁盤和內存之間使用,可以進行字符和字節之間快速切換,如 Java 的內存編碼就是采用 UTF-16 編碼。但是它不適合在網絡之間傳輸,因為網絡傳輸容易損壞字節流,一旦字節流損壞將很難恢復,想比較而言 UTF-8 更適合網絡傳輸,對 ASCII 字符采用單字節存儲,另外單個字符損壞也不會影響後面其它字符,在編碼效率上介於 GBK 和 UTF-16 之間,所以 UTF-8 在編碼效率上和編碼安全性上做了平衡,是理想的中文編碼方式。

4 常見問題分析

  當我們碰到一些亂碼時,應該怎麼處理這些問題?出現亂碼問題唯一的原因都是在 char 到 byte 或 byte 到 char 轉換中編碼和解碼的字符集不一致導致的,由於往往一次操作涉及到多次編解碼,所以出現亂碼時很難查找到底是哪個環節出現了問題,下面就幾種常見的現象進行分析。
中文變成了看不懂的字符

  例如,字符串“淘!我喜歡!”變成了“Ì Ô £ ¡Î Ò Ï²»¶ £ ¡”編碼過程如下圖所示

  字符串在解碼時所用的字符集與編碼字符集不一致導致漢字變成了看不懂的亂碼,而且是一個漢字字符變成兩個亂碼字符。
一個漢字變成一個問號

  例如,字符串“淘!我喜歡!”變成了“??????”編碼過程如下圖所示:

  將中文和中文符號經過不支持中文的 ISO-8859-1 編碼後,所有字符變成了“?”,這是因為用 ISO-8859-1 進行編解碼時遇到不在碼值范圍內的字符時統一用 3f 表示,這也就是通常所說的“黑洞”,所有 ISO-8859-1 不認識的字符都變成了“?”。

一個漢字變成兩個問號
  例如,字符串“淘!我喜歡!”變成了“????????????”編碼過程如下圖所示:

  這種情況比較復雜,中文經過多次編碼,但是其中有一次編碼或者解碼不對仍然會出現中文字符變成“?”現象,出現這種情況要仔細查看中間的編碼環節,找出出現編碼錯誤的地方。

參考:

  1、深入分析 Java 中的中文編碼問題

  2、《深入分析Java Web技術內幕》PDF 下載見 http://www.linuxidc.com/Linux/2016-10/135767.htm

Copyright © Linux教程網 All Rights Reserved