1. 只有 字符到字節(jié) 或者 字節(jié)到字符 的轉(zhuǎn)換才存在編碼轉(zhuǎn)碼;
2. Java String 采用 UTF-16 編碼方式存儲所有字符。unicode體系采用唯一的碼點表示唯一的字符信息, 碼點的存儲方式有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只有一種格式,可認為String是獨立于編碼系統(tǒng)的,通過 getBytes(String charsetName) 可實現(xiàn)編碼轉(zhuǎn)換。
4. String對象是內(nèi)存數(shù)據(jù),string之間不存在編碼變換問題。
5. 編碼轉(zhuǎn)換場景主要在 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") 這種用法是無意義的,甚至是錯誤的。這種用法是用GB18030編碼將ISO-8859-1編碼格式的字節(jié)數(shù)據(jù)強制轉(zhuǎn)換成unicode碼點,不亂碼是運氣!
9. 數(shù)據(jù)庫JDBC能夠處理 數(shù)據(jù)庫數(shù)據(jù) <=> String 的正確互換。
9. OutputStreamWriter 和 InputStreamWriter 應(yīng)該指定編碼格式,避免程序依賴操作系統(tǒng)默認編碼。
10. 用戶從瀏覽器端發(fā)起一個 HTTP 請求,需要存在編碼的地方是 URL、Cookie、Parameter。服務(wù)器端接受到 HTTP 請求后要解析 HTTP 協(xié)議,其中 URI、Cookie 和 POST 表單參數(shù)需要解碼,服務(wù)器端可能還需要讀取數(shù)據(jù)庫中的數(shù)據(jù),本地或網(wǎng)絡(luò)中其它地方的文本文件,這些數(shù)據(jù)都可能存在編碼問題,當 Servlet 處理完所有請求的數(shù)據(jù)后,需要將這些數(shù)據(jù)再編碼通過 Socket 發(fā)送到用戶請求的瀏覽器里,再經(jīng)過瀏覽器解碼成為文本。
11. tomcat: URL 的 URI 部分進行解碼的字符集是在 connector 的 <Connector URIEncoding=”UTF-8”/>
12. QueryString(GET 查詢參數(shù)) 的解碼字符集要么是 Header 中 ContentType 中定義的 Charset 要么就是默認的 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ù)器的傳遞過程中就不會丟失信息了,如果我們要訪問這些項時再按照相應(yīng)的字符集解碼就好了。
14. POST 表單的編解碼: 通過 HTTP 的 BODY 傳遞到服務(wù)端的。當我們在頁面上點擊 submit 按鈕時瀏覽器首先將根據(jù) ContentType 的 Charset 編碼格式對表單填的參數(shù)進行編碼然后提交到服務(wù)器端,在服務(wù)器端同樣也是用 ContentType 中字符集進行解碼。所以通過 POST 表單提交的參數(shù)一般不會出現(xiàn)問題,而且這個字符集編碼是我們自己設(shè)置的,可以通過 request.setCharacterEncoding(charset) 來設(shè)置。
15. HTTP BODY 的編解碼: 當用戶請求的資源已經(jīng)成功獲取后,這些內(nèi)容將通過 Response 返回給客戶端瀏覽器,這個過程先要經(jīng)過編碼再到瀏覽器進行解碼。這個過程的編解碼字符集可以通過 response.setCharacterEncoding 來設(shè)置,它將會覆蓋 request.getCharacterEncoding 的值,并且通過 Header 的 Content-Type 返回客戶端,瀏覽器接受到返回的 socket 流時將通過 Content-Type 的 charset 來解碼,如果返回的 HTTP Header 中 Content-Type 沒有設(shè)置 charset,那么瀏覽器將根據(jù) Html 的 <meta HTTP-equiv="Content-Type" content="text/html; charset=GBK" /> 中的 charset 來解碼。如果也沒有定義的話,那么瀏覽器將使用默認的編碼來解碼。<%@ 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)換: 將文件存儲字節(jié)轉(zhuǎn)成unicode存入String對象內(nèi)存. 采用文件編碼 char[] charArray = cn.toCharArray(); byte[] data = cn.getBytes(); System.out.println("print char array : " + cn); toHex(cn.toCharArray()); cn = "���ı���"; // 這里存在編碼轉(zhuǎn)換: 將文件存儲字節(jié)轉(zhuǎn)成unicode存入String對象內(nèi)存. 采用文件編碼。 // 顯示亂碼是由于文件采用的編碼無法解碼文件存儲字節(jié)數(shù)據(jù)。故存到String的unicode也是亂碼的 charArray = cn.toCharArray(); System.out.println("print char array: " + cn); toHex(cn.toCharArray()); encode(); } }
幾種常見的編碼格式
為什么要編碼
不知道大家有沒有想過一個問題,那就是為什么要編碼?我們能不能不編碼?要回答這個問題必須要回到計算機是如何表示我們?nèi)祟惸軌蚶斫獾姆柕,這些符號也就是我們?nèi)祟愂褂玫恼Z言。由于人類的語言有太多,因而表示這些語言的符號太多,無法用計算機中一個基本的存儲單元—— byte 來表示,因而必須要經(jīng)過拆分或一些翻譯工作,才能讓計算機能理解。我們可以把計算機能夠理解的語言假定為英語,其它語言要能夠在計算機中使用必須經(jīng)過一次翻譯,把它翻譯成英語。這個翻譯的過程就是編碼。所以可以想象只要不是說英語的國家要能夠使用計算機就必須要經(jīng)過編碼。這看起來有些霸道,但是這就是現(xiàn)狀,這也和我們國家現(xiàn)在在大力推廣漢語一樣,希望其它國家都會說漢語,以后其它的語言都翻譯成漢語,我們可以把計算機中存儲信息的最小單位改成漢字,這樣我們就不存在編碼問題了。
所以總的來說,編碼的原因可以總結(jié)為:
計算機中存儲信息的最小單元是一個字節(jié)即 8 個 bit,所以能表示的字符范圍是 0~255 個
人類要表示的符號太多,無法用一個字節(jié)來完全表示
要解決這個矛盾必須需要一個新的數(shù)據(jù)結(jié)構(gòu) char,從 char 到 byte 必須編碼
如何“翻譯”
明白了各種語言需要交流,經(jīng)過翻譯是必要的,那又如何來翻譯呢?計算中提拱了多種翻譯方式,常見的有 ASCII、ISO-8859-1、GB2312、GBK、UTF-8、UTF-16 等。它們都可以被看作為字典,它們規(guī)定了轉(zhuǎn)化的規(guī)則,按照這個規(guī)則就可以讓計算機正確的表示我們的字符。目前的編碼格式很多,例如 GB2312、GBK、UTF-8、UTF-16 這幾種格式都可以表示一個漢字,那我們到底選擇哪種編碼格式來存儲漢字呢?這就要考慮到其它因素了,是存儲空間重要還是編碼的效率重要。根據(jù)這些因素來正確選擇編碼格式,下面簡要介紹一下這幾種編碼格式。
ASCII 碼
學過計算機的人都知道 ASCII 碼,總共有 128 個,用一個字節(jié)的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入并且能夠顯示出來。
ISO-8859-1
128 個字符顯然是不夠用的,于是 ISO 組織在 ASCII 碼基礎(chǔ)上又制定了一些列標準用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數(shù)西歐語言字符,所有應(yīng)用的最廣泛。ISO-8859-1 仍然是單字節(jié)編碼,它總共能表示 256 個字符。
GB2312
它的全稱是《信息交換用漢字編碼字符集 基本集》,它是雙字節(jié)編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號區(qū),總共包含 682 個符號,從 B0-F7 是漢字區(qū),包含 6763 個漢字。
GBK
全稱叫《漢字內(nèi)碼擴展規(guī)范》,是國家技術(shù)監(jiān)督局為 windows95 所制定的新的漢字內(nèi)碼規(guī)范,它的出現(xiàn)是為了擴展 GB2312,加入更多的漢字,它的編碼范圍是 8140~FEFE(去掉 XX7F)總共有 23940 個碼位,它能表示 21003 個漢字,它的編碼是和 GB2312 兼容的,也就是說用 GB2312 編碼的漢字可以用 GBK 來解碼,并且不會有亂碼。
GB18030
全稱是《信息交換用漢字編碼字符集》,是我國的強制標準,它可能是單字節(jié)、雙字節(jié)或者四字節(jié)編碼,它的編碼與 GB2312 編碼兼容,這個雖然是國家標準,但是實際應(yīng)用系統(tǒng)中使用的并不廣泛。
UTF-16
說到 UTF 必須要提到 Unicode(Universal Code 統(tǒng)一碼),ISO 試圖想創(chuàng)建一個全新的超語言字典,世界上所有的語言都可以通過這本字典來相互翻譯?上攵@個字典是多么的復雜,關(guān)于 Unicode 的詳細規(guī)范可以參考相應(yīng)文檔。Unicode 是 Java 和 XML 的基礎(chǔ),下面詳細介紹 Unicode 在計算機中的存儲形式。
UTF-16 具體定義了 Unicode 字符在計算機中存取方法。UTF-16 用兩個字節(jié)來表示 Unicode 轉(zhuǎn)化格式,這個是定長的表示方法,不論什么字符都可以用兩個字節(jié)表示,兩個字節(jié)是 16 個 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每兩個字節(jié)表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為內(nèi)存的字符存儲格式的一個很重要的原因。
UTF-8
UTF-16 統(tǒng)一采用兩個字節(jié)表示一個字符,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字符用一個字節(jié)就可以表示的現(xiàn)在要兩個字節(jié)表示,存儲空間放大了一倍,在現(xiàn)在的網(wǎng)絡(luò)帶寬還非常有限的今天,這樣會增大網(wǎng)絡(luò)傳輸?shù)牧髁,而且也沒必要。而 UTF-8 采用了一種變長技術(shù),每個編碼區(qū)域有不同的字碼長度。不同類型的字符可以是由 1~6 個字節(jié)組成。
UTF-8 有以下編碼規(guī)則:
如果一個字節(jié),最高位(第 8 位)為 0,表示這是一個 ASCII 字符(00 - 7F)?梢,所有 ASCII 編碼已經(jīng)是 UTF-8 了。
如果一個字節(jié),以 11 開頭,連續(xù)的 1 的個數(shù)暗示這個字符的字節(jié)數(shù),例如:110xxxxx 代表它是雙字節(jié) UTF-8 字符的首字節(jié)。
如果一個字節(jié),以 10 開始,表示它不是首字節(jié),需要向前查找才能得到當前字符的首字節(jié)