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

首頁(yè)編程開(kāi)發(fā)java → Java編程中的編碼問(wèn)題、Java 中的中文編碼問(wèn)題

Java編程中的編碼問(wèn)題、Java 中的中文編碼問(wèn)題

前往專(zhuān)題相關(guān)軟件相關(guān)文章發(fā)表評(píng)論 來(lái)源:西西整理時(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)的,通過(guò)  getBytes(String charsetName) 可實(shí)現(xiàn)編碼轉(zhuǎn)換。

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

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

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

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

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

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

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

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

12. QueryString(GET 查詢(xún)參數(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ù)器的傳遞過(guò)程中就不會(huì)丟失信息了,如果我們要訪問(wèn)這些項(xiàng)時(shí)再按照相應(yīng)的字符集解碼就好了。

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

15. HTTP BODY 的編解碼: 當(dāng)用戶(hù)請(qǐng)求的資源已經(jīng)成功獲取后,這些內(nèi)容將通過(guò) Response 返回給客戶(hù)端瀏覽器,這個(gè)過(guò)程先要經(jīng)過(guò)編碼再到瀏覽器進(jìn)行解碼。這個(gè)過(guò)程的編解碼字符集可以通過(guò) response.setCharacterEncoding 來(lái)設(shè)置,它將會(huì)覆蓋 request.getCharacterEncoding 的值,并且通過(guò) Header 的 Content-Type 返回客戶(hù)端,瀏覽器接受到返回的 socket 流時(shí)將通過(guò) Content-Type 的 charset 來(lái)解碼,如果返回的 HTTP Header 中 Content-Type 沒(méi)有設(shè)置 charset,那么瀏覽器將根據(jù) Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 來(lái)解碼。如果也沒(méi)有定義的話,那么瀏覽器將使用默認(rèn)的編碼來(lái)解碼。<%@ 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)存. 采用文件編碼。 
                        // 顯示亂碼是由于文件采用的編碼無(wú)法解碼文件存儲(chǔ)字節(jié)數(shù)據(jù)。故存到String的unicode也是亂碼的
        charArray = cn.toCharArray();
        System.out.println("print char array: " + cn);
        toHex(cn.toCharArray());
        
        encode();
    }
}

幾種常見(jiàn)的編碼格式

為什么要編碼

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

所以總的來(lái)說(shuō),編碼的原因可以總結(jié)為:

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

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

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

如何“翻譯”

明白了各種語(yǔ)言需要交流,經(jīng)過(guò)翻譯是必要的,那又如何來(lái)翻譯呢?計(jì)算中提拱了多種翻譯方式,常見(jiàn)的有 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è)漢字,那我們到底選擇哪種編碼格式來(lái)存儲(chǔ)漢字呢?這就要考慮到其它因素了,是存儲(chǔ)空間重要還是編碼的效率重要。根據(jù)這些因素來(lái)正確選擇編碼格式,下面簡(jiǎn)要介紹一下這幾種編碼格式。

ASCII 碼

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

ISO-8859-1

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

GB2312

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

GBK

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

GB18030

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

UTF-16

說(shuō)到 UTF 必須要提到 Unicode(Universal Code 統(tǒng)一碼),ISO 試圖想創(chuàng)建一個(gè)全新的超語(yǔ)言字典,世界上所有的語(yǔ)言都可以通過(guò)這本字典來(lái)相互翻譯?上攵@個(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é)來(lái)表示 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ù)牧髁,而且也沒(méi)必要。而 UTF-8 采用了一種變長(zhǎng)技術(shù),每個(gè)編碼區(qū)域有不同的字碼長(zhǎng)度。不同類(lèi)型的字符可以是由 1~6 個(gè)字節(jié)組成。

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

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

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

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