西西軟件園多重安全檢測(cè)下載網(wǎng)站、值得信賴的軟件下載站!
軟件
軟件
文章
搜索

首頁編程開發(fā)java → Java編程中的編碼問題、Java 中的中文編碼問題

Java編程中的編碼問題、Java 中的中文編碼問題

前往專題相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來源:西西整理時(shí)間:2013/9/8 10:23:59字體大。A-A+

作者:西西點(diǎn)擊:79次評(píng)論:0次標(biāo)簽: Java

1. 只有 字符到字節(jié) 或者 字節(jié)到字符 的轉(zhuǎn)換才存在編碼轉(zhuǎn)碼;

2. Java String 采用 UTF-16 編碼方式存儲(chǔ)所有字符。unicode體系采用唯一的碼點(diǎn)表示唯一的字符信息, 碼點(diǎn)的存儲(chǔ)方式有UFT-16、UTF-8 等等。:  A String represents a string in the UTF-16 format in which supplementary characters are represented bysurrogate pairs (see the section Unicode Character Representations in the Character class for more information). Index values refer to char code units, so a supplementary character uses two positions in a String.  The String class provides methods for dealing with Unicode code points (i.e., characters), in addition to those for dealing with Unicode code units (i.e., char values).

3. String只有一種格式,可認(rèn)為String是獨(dú)立于編碼系統(tǒng)的,通過  getBytes(String charsetName) 可實(shí)現(xiàn)編碼轉(zhuǎn)換。

4. String對(duì)象是內(nèi)存數(shù)據(jù),string之間不存在編碼變換問題。 

5. 編碼轉(zhuǎn)換場(chǎng)景主要在 I/O , I/O 包括磁盤 I/O 和網(wǎng)絡(luò) I/O:文件輸入輸出、屏幕、數(shù)據(jù)庫、瀏覽器、服務(wù)器。

6. 在內(nèi)存中倒騰String數(shù)據(jù)是編碼無關(guān)的,比如壓縮編碼。

7. 編碼誤區(qū): new String(str.getBytes("ISO-8859-1"), "GB18030") 這種用法是無意義的,甚至是錯(cuò)誤的。這種用法是用GB18030編碼將ISO-8859-1編碼格式的字節(jié)數(shù)據(jù)強(qiáng)制轉(zhuǎn)換成unicode碼點(diǎn),不亂碼是運(yùn)氣!

9. 數(shù)據(jù)庫JDBC能夠處理 數(shù)據(jù)庫數(shù)據(jù) <=> String 的正確互換。

9. OutputStreamWriter 和 InputStreamWriter 應(yīng)該指定編碼格式,避免程序依賴操作系統(tǒng)默認(rèn)編碼。

10. 用戶從瀏覽器端發(fā)起一個(gè) HTTP 請(qǐng)求,需要存在編碼的地方是 URL、Cookie、Parameter。服務(wù)器端接受到 HTTP 請(qǐng)求后要解析 HTTP 協(xié)議,其中 URI、Cookie 和 POST 表單參數(shù)需要解碼,服務(wù)器端可能還需要讀取數(shù)據(jù)庫中的數(shù)據(jù),本地或網(wǎng)絡(luò)中其它地方的文本文件,這些數(shù)據(jù)都可能存在編碼問題,當(dāng) Servlet 處理完所有請(qǐng)求的數(shù)據(jù)后,需要將這些數(shù)據(jù)再編碼通過 Socket 發(fā)送到用戶請(qǐng)求的瀏覽器里,再經(jīng)過瀏覽器解碼成為文本。

11. tomcat: URL 的 URI 部分進(jìn)行解碼的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/>

12. QueryString(GET 查詢參數(shù)) 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認(rèn)的 ISO-8859-1,要使用 ContentType 中定義的編碼就要設(shè)置 connector 的 <Connector URIEncoding=”UTF-8” useBodyEncodingForURI=”true”/> 中的 useBodyEncodingForURI 設(shè)置為 true。

13. 不要在 Header 中傳遞非 ASCII 字符,如果一定要傳遞的話,我們可以先將這些字符用 org.apache.catalina.util.URLEncoder 編碼然后再添加到 Header 中,這樣在瀏覽器到服務(wù)器的傳遞過程中就不會(huì)丟失信息了,如果我們要訪問這些項(xiàng)時(shí)再按照相應(yīng)的字符集解碼就好了。

14. POST 表單的編解碼: 通過 HTTP 的 BODY 傳遞到服務(wù)端的。當(dāng)我們?cè)陧撁嫔宵c(diǎn)擊 submit 按鈕時(shí)瀏覽器首先將根據(jù) ContentType 的 Charset 編碼格式對(duì)表單填的參數(shù)進(jìn)行編碼然后提交到服務(wù)器端,在服務(wù)器端同樣也是用 ContentType 中字符集進(jìn)行解碼。所以通過 POST 表單提交的參數(shù)一般不會(huì)出現(xiàn)問題,而且這個(gè)字符集編碼是我們自己設(shè)置的,可以通過 request.setCharacterEncoding(charset) 來設(shè)置。

15. HTTP BODY 的編解碼: 當(dāng)用戶請(qǐng)求的資源已經(jīng)成功獲取后,這些內(nèi)容將通過 Response 返回給客戶端瀏覽器,這個(gè)過程先要經(jīng)過編碼再到瀏覽器進(jìn)行解碼。這個(gè)過程的編解碼字符集可以通過 response.setCharacterEncoding 來設(shè)置,它將會(huì)覆蓋 request.getCharacterEncoding 的值,并且通過 Header 的 Content-Type 返回客戶端,瀏覽器接受到返回的 socket 流時(shí)將通過 Content-Type 的 charset 來解碼,如果返回的 HTTP Header 中 Content-Type 沒有設(shè)置 charset,那么瀏覽器將根據(jù) Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 來解碼。如果也沒有定義的話,那么瀏覽器將使用默認(rèn)的編碼來解碼。<%@ page contentType="text/html; charset= GBK" %>。該設(shè)置和response.setCharacterEncoding("GBK")等效。

示例代碼

/**
 * @author zhenjing
 * 
 * @date 2013-9-7
 */
public class cnCodeTest {

    public static void toHex(char[] b) {
        for (int i = 0; i < b.length; i++) {
            System.out.printf("%x " , (int)b[i]);
        }
        System.out.println();
    }

    public static void toHex(byte[] b) {
        for (int i = 0; i < b.length; i++) {
            System.out.printf("%x " , b[i]);
        }
        System.out.println();
    }
    
    public static void encode() {
        String name = "I am 中文編碼";
        toHex(name.toCharArray());
        try {
            byte[] iso8859 = name.getBytes("ISO-8859-1");
            toHex(iso8859);
            byte[] gb2312 = name.getBytes("GB2312");
            toHex(gb2312);
            byte[] gbk = name.getBytes("GBK");
            toHex(gbk);
            byte[] utf16 = name.getBytes("UTF-16");
            toHex(utf16);
            byte[] utf8 = name.getBytes("UTF-8");
            toHex(utf8);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        String cn = "中文編碼";  // 這里存在編碼轉(zhuǎn)換: 將文件存儲(chǔ)字節(jié)轉(zhuǎn)成unicode存入String對(duì)象內(nèi)存. 采用文件編碼

        char[] charArray = cn.toCharArray();
        byte[] data = cn.getBytes();

        System.out.println("print char array : " + cn);
        toHex(cn.toCharArray());

        cn = "���ı���"; // 這里存在編碼轉(zhuǎn)換: 將文件存儲(chǔ)字節(jié)轉(zhuǎn)成unicode存入String對(duì)象內(nèi)存. 采用文件編碼。 
                        // 顯示亂碼是由于文件采用的編碼無法解碼文件存儲(chǔ)字節(jié)數(shù)據(jù)。故存到String的unicode也是亂碼的
        charArray = cn.toCharArray();
        System.out.println("print char array: " + cn);
        toHex(cn.toCharArray());
        
        encode();
    }
}

幾種常見的編碼格式

為什么要編碼

不知道大家有沒有想過一個(gè)問題,那就是為什么要編碼?我們能不能不編碼?要回答這個(gè)問題必須要回到計(jì)算機(jī)是如何表示我們?nèi)祟惸軌蚶斫獾姆?hào)的,這些符號(hào)也就是我們?nèi)祟愂褂玫恼Z言。由于人類的語言有太多,因而表示這些語言的符號(hào)太多,無法用計(jì)算機(jī)中一個(gè)基本的存儲(chǔ)單元—— byte 來表示,因而必須要經(jīng)過拆分或一些翻譯工作,才能讓計(jì)算機(jī)能理解。我們可以把計(jì)算機(jī)能夠理解的語言假定為英語,其它語言要能夠在計(jì)算機(jī)中使用必須經(jīng)過一次翻譯,把它翻譯成英語。這個(gè)翻譯的過程就是編碼。所以可以想象只要不是說英語的國(guó)家要能夠使用計(jì)算機(jī)就必須要經(jīng)過編碼。這看起來有些霸道,但是這就是現(xiàn)狀,這也和我們國(guó)家現(xiàn)在在大力推廣漢語一樣,希望其它國(guó)家都會(huì)說漢語,以后其它的語言都翻譯成漢語,我們可以把計(jì)算機(jī)中存儲(chǔ)信息的最小單位改成漢字,這樣我們就不存在編碼問題了。

所以總的來說,編碼的原因可以總結(jié)為:

計(jì)算機(jī)中存儲(chǔ)信息的最小單元是一個(gè)字節(jié)即 8 個(gè) bit,所以能表示的字符范圍是 0~255 個(gè)

人類要表示的符號(hào)太多,無法用一個(gè)字節(jié)來完全表示

要解決這個(gè)矛盾必須需要一個(gè)新的數(shù)據(jù)結(jié)構(gòu) char,從 char 到 byte 必須編碼

如何“翻譯”

明白了各種語言需要交流,經(jīng)過翻譯是必要的,那又如何來翻譯呢?計(jì)算中提拱了多種翻譯方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它們都可以被看作為字典,它們規(guī)定了轉(zhuǎn)化的規(guī)則,按照這個(gè)規(guī)則就可以讓計(jì)算機(jī)正確的表示我們的字符。目前的編碼格式很多,例如 GB2312、GBK、UTF-8、UTF-16 這幾種格式都可以表示一個(gè)漢字,那我們到底選擇哪種編碼格式來存儲(chǔ)漢字呢?這就要考慮到其它因素了,是存儲(chǔ)空間重要還是編碼的效率重要。根據(jù)這些因素來正確選擇編碼格式,下面簡(jiǎn)要介紹一下這幾種編碼格式。

ASCII 碼

學(xué)過計(jì)算機(jī)的人都知道 ASCII 碼,總共有 128 個(gè),用一個(gè)字節(jié)的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入并且能夠顯示出來。

ISO-8859-1

128 個(gè)字符顯然是不夠用的,于是 ISO 組織在 ASCII 碼基礎(chǔ)上又制定了一些列標(biāo)準(zhǔn)用來擴(kuò)展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數(shù)西歐語言字符,所有應(yīng)用的最廣泛。ISO-8859-1 仍然是單字節(jié)編碼,它總共能表示 256 個(gè)字符。

GB2312

它的全稱是《信息交換用漢字編碼字符集 基本集》,它是雙字節(jié)編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號(hào)區(qū),總共包含 682 個(gè)符號(hào),從 B0-F7 是漢字區(qū),包含 6763 個(gè)漢字。

GBK

全稱叫《漢字內(nèi)碼擴(kuò)展規(guī)范》,是國(guó)家技術(shù)監(jiān)督局為 windows95 所制定的新的漢字內(nèi)碼規(guī)范,它的出現(xiàn)是為了擴(kuò)展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個(gè)碼位,它能表示 21003 個(gè)漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,并且不會(huì)有亂碼。

GB18030

全稱是《信息交換用漢字編碼字符集》,是我國(guó)的強(qiáng)制標(biāo)準(zhǔn),它可能是單字節(jié)、雙字節(jié)或者四字節(jié)編碼,它的編碼與 GB2312 編碼兼容,這個(gè)雖然是國(guó)家標(biāo)準(zhǔn),但是實(shí)際應(yīng)用系統(tǒng)中使用的并不廣泛。

UTF-16

說到 UTF 必須要提到 Unicode(Universal Code 統(tǒng)一碼),ISO 試圖想創(chuàng)建一個(gè)全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯?上攵@個(gè)字典是多么的復(fù)雜,關(guān)于 Unicode 的詳細(xì)規(guī)范可以參考相應(yīng)文檔。Unicode 是 Java 和 XML 的基礎(chǔ),下面詳細(xì)介紹 Unicode 在計(jì)算機(jī)中的存儲(chǔ)形式。

UTF-16 具體定義了 Unicode 字符在計(jì)算機(jī)中存取方法。UTF-16 用兩個(gè)字節(jié)來表示 Unicode 轉(zhuǎn)化格式,這個(gè)是定長(zhǎng)的表示方法,不論什么字符都可以用兩個(gè)字節(jié)表示,兩個(gè)字節(jié)是 16 個(gè) bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每?jī)蓚(gè)字節(jié)表示一個(gè)字符,這個(gè)在字符串操作時(shí)就大大簡(jiǎn)化了操作,這也是 Java 以 UTF-16 作為內(nèi)存的字符存儲(chǔ)格式的一個(gè)很重要的原因。

UTF-8

UTF-16 統(tǒng)一采用兩個(gè)字節(jié)表示一個(gè)字符,雖然在表示上非常簡(jiǎn)單方便,但是也有其缺點(diǎn),有很大一部分字符用一個(gè)字節(jié)就可以表示的現(xiàn)在要兩個(gè)字節(jié)表示,存儲(chǔ)空間放大了一倍,在現(xiàn)在的網(wǎng)絡(luò)帶寬還非常有限的今天,這樣會(huì)增大網(wǎng)絡(luò)傳輸?shù)牧髁,而且也沒必要。而 UTF-8 采用了一種變長(zhǎng)技術(shù),每個(gè)編碼區(qū)域有不同的字碼長(zhǎng)度。不同類型的字符可以是由 1~6 個(gè)字節(jié)組成。

UTF-8 有以下編碼規(guī)則:

如果一個(gè)字節(jié),最高位(第 8 位)為 0,表示這是一個(gè) ASCII 字符(00 - 7F)?梢姡 ASCII 編碼已經(jīng)是 UTF-8 了。

如果一個(gè)字節(jié),以 11 開頭,連續(xù)的 1 的個(gè)數(shù)暗示這個(gè)字符的字節(jié)數(shù),例如:110xxxxx 代表它是雙字節(jié) UTF-8 字符的首字節(jié)。

如果一個(gè)字節(jié),以 10 開始,表示它不是首字節(jié),需要向前查找才能得到當(dāng)前字符的首字節(jié)