編碼字符集

本文介紹了編碼字符集的概念以及Java與編碼字符集之間的關係,文章的內容來自於本人工做過程當中的經驗積累以及網絡中的相關文章介紹,若是文章中有任何紕漏歡迎讀者指正,讓咱們共同討論學習Jjava

1.      字符數據庫

字符是抽象的最小文本單位。它沒有固定的形狀(多是一個字形),並且沒有值。「A」是一個字符,「€」(德國、法國和許多其餘歐洲國家通用貨幣的標誌)也是一個字符。「中」「國」這是兩個漢字字符。字符僅僅表明一個符號,沒有任何實際值的意義。

編程

2.      字符集網絡

字符集是字符的集合。例如,漢字字符是中國人最早發明的字符,在中文、日文、韓文和越南文的書寫中使用。這也說明了字符和字符集之間的關係,字符組成字符集。

編程語言

3.      編碼字符集ide

編碼字符集是一個字符集(有時候也被簡稱位字符集),它爲每個字符分配一個惟一數字。最先的編碼是iso8859-1,和ascii編碼類似。但爲了方便表示各類各樣的語言,逐漸出現了不少標準編碼。性能

 

iso8859-1:屬於單字節編碼字符集,最多能表示的字符範圍是0-255,應用於英文系列,除了iso8859-1之外還有其餘iso8859系列的編碼,這些編碼都是爲了知足歐洲國家語言字符的須要而設計的。學習

 

GB2312/GBK/ GB18030:前面提到的iso8859-1最多隻能表示256個字符,這對於漢字來講實在是有些抱歉,因此就有了如今要介紹的漢字國標碼,專門用來表示漢字,是雙字節編碼字符集,而英文字母和iso8859-1一致(兼容iso8859-1編碼)。其中GBK編碼可以用來同時表示繁體字和簡體字,而GB2312只能表示簡體字,GBK是兼容GB2312編碼的。而GB18030-2000則是一個更復雜的字符集,採用變長字節的編碼方式,可以支持更多的字符。須要注意的是中國政府要求全部在中國出售的軟件必須支持GB18030。優化

 

Unicode:這是最統一的編碼字符集,能夠用來表示全部語言的字符,不兼容任何前面提到的編碼字符集。Unicode 標準始終使用十六進制數字,並且在書寫時在前面加上前綴「U+」,因此「A」的編碼書寫爲「U+0041」。注意:在JAVA語言中書寫時應該使用轉義符‘\u’表示,如 char charA = ‘\u0041’; 這種表示方法等與 char charA = ‘A’; 。編碼

 

從ASCII(英文) ==> 西歐文字 ==> 東歐字符集(俄文,希臘語等) ==> 東亞字符集(GB2312 BIG5 SJIS等)==> 擴展字符集GBK GB18030這個發展過程基本上也反映了字符集標準的發展過程,但這麼隨着時間的推移,尤爲是互聯網讓跨語言的信息的交互變得愈來愈多的時候,太多多針對本地語言的編碼標準的出現致使一個應用程序的國際化變得成本很是高。尤爲是你要編寫一個同時包含法文和簡體中文的文檔,這時候通常都會想到要是用一個通用的字符集可以顯示全部語言的全部文字就行了,並且這樣作應用也可以比較方便的國際化,爲了達到這個目標,即便應用犧牲一些空間和程序效率也是很是值得的。UNICODE就是這樣一個通用的解決方案。

 

4.      Unicode編碼字符集

Unicode 由於必須將中、韓、日、英、法、阿拉伯……等許多國家所使用的文字都歸入,目前已經包含了六萬多個字符,因此Unicode 使用了16個位來爲字符編碼。由於 Unicode 使用了 16 位編碼,因此每一個字符都用 16 位來儲存或傳輸是很天然的事,這種儲存或傳輸的格式稱爲UTF-16(一種Unicode的字符編碼方案,在這裏所說的UTF-16並不涉及增補字符的表示,本文將會在稍後介紹)。可是若是你使用到的字符都是西方字符,那麼你必定不會想用 UTF-16 的格式,由於體積比8位的iso8859-1多了一倍,如此一來就必須考慮程序運行時各類字符在內存中所佔空間的性能問題,這便引入了字符編碼方案的概念:

字符編碼方案是從一個或多個編碼字符集到一個或多個固定寬度代碼單元序列的映射。

最經常使用的代碼單元是字節,因此能夠簡單的認爲字符編碼方案是爲了告訴計算機如何將編碼字符集(如Unicode)映射到計算機能夠識別的數據格式中,如字節。這種編碼方案每每可以爲他所對應的字符集在計算機處理時提供更爲優化的空間以及性能上的解決方案。Unicode編碼字符集有三種字符編碼方案,下面將逐一介紹:

 

l         UTF-32* 即將每個Unicode編碼表示爲相同值的32位整數。很明顯,它是內部處理最方便的表達方式,可是,若是做爲通常字符串表達方式,則要消耗更多的內存。顯而易見,對於英文字母的表示將須要多個0字節,僅僅由於咱們須要4個字節32位來表示一個Unicode字符。

 

l         UTF-16 使用一個或兩個未分配的 16位代碼單元的序列對Unicode編碼進行編碼。值U+0000至U+FFFF 編碼爲一個相同值的16位單元。增補字符*編碼爲兩個代碼單元,第一個單元來自於高代理範圍(U+D800 至 U+DBFF),第二個單元來自於低代理範圍(U+DC00 至U+DFFF)。這在概念上可能看起來相似於多字節編碼,可是其中有一個重要區別:值 U+D800 至 U+DFFF 保留用於 UTF-16;沒有這些值分配字符做爲代碼點。這意味着,對於一個字符串中的每一個單獨的代碼單元,軟件能夠識別是否該代碼單元表示某個單單元字符,或者是否該代碼單元是某個雙單元字符的第一個或第二單元。這至關於某些傳統的多字節字符編碼來講是一個顯著的改進,在傳統的多字節字符編碼中,字節值0x41 既可能表示字母「A」,也多是一個雙字節字符的第二個字節。

 

l         UTF-8 使用一至四個字節的序列對編碼Unicode進行編碼。U+0000至U+007F使用一個字節編碼,U+0080至U+07FF 使用兩個字節,U+0800 至U+FFFF使用三個字節,而U+10000至U+10FFFF使用四個字節。UTF-8設計原理爲:字節值0x00至0x7F始終表示代碼點U+0000至U+007F(Basic Latin 字符子集,它對應 ASCII 字符集)。這些字節值永遠不會表示其餘Unicode編碼字符,這一特性使 UTF-8 能夠很方便地在軟件中將特殊的含義賦予某些 ASCII 字符。UTF-8 的格式在編碼英文時,只須要8位,可是中文則是24位,其餘更加偏僻的字符才又多是32位,這也是UTF-8最大的編碼特色,能夠最高效率的利用計算機空間,由於在計算機處理的時候大多數狀況下仍是隻使用英文進行運算和處理,這也是爲何還須要UTF-8的主要緣由,由於畢竟互聯網70%以上的信息仍然是英文。若是連英文都用2個字節存取(UCS-2),空間浪費不就太多了?

 

 

*  UTF­-32 表示Unicode Transformation Form 32-bit form,UTF-16,UTF-8依此類推。

*  Unicode 最初設計是做爲一種固定寬度的 16 位字符編碼。在 Java 編程語言中,基本數據類型 char 初衷是經過提供一種簡單的、可以包含任何字符的數據類型來充分利用這種設計的優勢。不過,如今看來,16 位編碼的全部65,536個字符並不能徹底表示全世界全部正在使用或曾經使用的字符。因而,Unicode 標準已擴展到包含多達 1,112,064個字符。那些超出原來的16位限制的字符被稱做增補字符。

5.      Java與編碼字符集

從上面的介紹咱們知道了Unicode編碼字符集能夠用來表示世界上全部的語言文字。Java內部處理字符使用的字序方式是Unicode,這是一種通行全球的編碼方式,他使Java語言可以描述世界上全部的文字。在Java程序中對各類字符在內存中處理是使用Unicode的UTF-8編碼方式,這也是由於UTF-8的特色所決定的,Class File(也就是bytecode)中有一欄位叫作常數區(Constant Pool),一概使用UTF-8爲子元編碼。

 

這看起來一切正常,Java能夠處理世界上全部的字符,一切都是按照秩序在運行,可是,從前面的討論咱們知道,世界上並非僅僅只有Unicode編碼字符集,同時存在的還有iso8859-一、GBK等編碼字符集,就是在Unicode中也一樣存在着UTF-8,UTF-16,UTF-32等多種編碼,若是傳入的字節編碼採用的是GB18030,而採用的解碼方式爲UTF-8那會有什麼後果呢,看看下面的代碼片斷:

 

public static final String TEST_RESOURCE = "你好";

 

    public static void testEncoding() {

        try {

            byte[] bytes = TEST_RESOURCE.getBytes("GB18030");

            String result = new String(bytes, "UTF-8");

            System.out.println("Receive value: [" + result + "].");

        } catch (UnsupportedEncodingException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

執行以上的代碼片斷,在個人機器(Win XP中文版)上面獲得的結果是:

       Receive value: [���].

明白了吧,這就是久負盛名的亂碼問題的根源,目前在市面上存在有多種編碼字符集,以及編碼字符集的編碼方案,因此雖然在Java中內部是以Unicode的UTF-8來處理各類字符的表示以及運算,可是這僅僅是在Java內部而以,若是Java程序須要和外部應用系統進行交互,好比與操做系統,數據庫系統之間的交互,那麼在這些交互過程當中如何處理字符集的編碼解碼是解決好Java應用程序亂碼問題的根源。

若是將上面的代碼塊修改爲以下的代碼塊:

    public static void testEncoding() {

        try {

            byte[] bytes = TEST_RESOURCE.getBytes("GB18030");

            String result = new String(bytes, "GB18030");

            System.out.println("Receive value: [" + result + "].");

        } catch (UnsupportedEncodingException e) {

            // TODO Auto-generated catch block

            e.printStackTrace();

        }

    }

                     注意紅色標註的地方,執行以上的代碼塊將會受到預期的結果:

                            Receive value: [你好].

統一字符的編碼類型和解碼類型,如此一來任何亂碼問題都不會再是問題了。在網上能夠搜索到N多的關於如何解決J2EE亂碼問題的文章,我在這裏也就不廢話了,我只是想說說Java亂碼問題的根源之所在。

若是你仔細想一想Java的開發過程,原文件編寫、javac編譯、java執行,這每一步驟都會涉及到編碼的轉換過程,這個過程老是存在的,只是有的時候用默認的參數進行。

咱們從javac這個命令來開始咱們的分析,編譯的時候,若是你不說明源文件編碼方式的話,javac 編譯器在讀進此原始程序文件開始編譯以前,會先去詢問操做系統檔案預設的編碼方式爲什麼。以個人操做系統WIN XP 中文版來講,javac 會先詢問WIN XP,得知當前的編碼是用GB18030的方式編碼。而後就能夠將源文件由GB18030轉成Unicode編碼方式,開始進行編譯。在這裏就會發生一下一些編碼問題:

 

l         若是操做系統的國籍資料設定錯誤,會形成javac編譯器取得的編碼信息是錯誤的,這裏也有可能因爲系統屬性file.encoding設置錯誤,在個人系統中該屬性爲GB18030,能夠經過代碼System.out.println(System.getProperties());輸出可能的系統屬性。

 

l         較差勁的編譯器可能沒有主動詢問操做系統的編碼方式,而是採用編譯器預設的編碼方式,固然這種狀況對於目前先進的編譯器來講已經不存在了,可是這確實是一種可能的緣由。

 

l         源代碼是在英文操做系統上書寫採用編碼iso8859-1,寫好之後再將源代碼傳遞給中文操做系統進行編譯,這樣因爲兩個操做系統的編碼方式不一樣,也會形成javac執行錯誤。

 

明白了吧,這些問題在咱們平常的代碼編寫過程當中,每每因爲默認的屬性都正好能知足咱們的須要,即源代碼的書寫以及編譯都採用操做系統默認的編碼方式,因此可能不少人到目前爲止都沒有碰見過諸如此類的問題,可是咱們要知道,這些問題確實是存在的。

Java編譯器在執行過程當中給咱們提供了可選的encoding參數來告訴編譯器該採用何種編碼方式將讀入的源文件轉換成Unicode編碼方式,而後再進行後續的編譯工做。

javac –encoding GB18030 ….

 

6.      Inside

OK,經過前面的介紹但願讀者可以對Java以及各類字符編碼之間的關係有個簡單的瞭解,下面我在繼續總結一下:

 

l         Javac是以系統默認編碼(file.encoding系統屬性)讀入源文件,而後按Unicode進行編碼的。

 

l         在JAVA運行的時候,JAVA也是採用Unicode編碼的,爲了高度利用內存空間提升效率對Unicode字符編碼採用了UTF-8的方式編碼,而且默認輸入和輸出的都是操做系統的默認編碼。

 

l         也就是說在new String(bytes,encode)中,系統認爲輸入的是編碼爲encode的字節流,換句話說,若是按encode來翻譯bytes才能獲得正確的結果;而在new String(bytes)中採用的就是根據file.encoding系統屬性讀入的編碼方式來進行編碼,一樣也必須根據系統默認的編碼才能獲得正確的結果,這個結果最後要在JAVA中保存,它仍是要從這個encode轉換成Unicode,由於在JAVA中各類字符均是以Unicode的形式來處理的。

 

l         也就是說有bytes-->encode字符-->Unicode字符的轉換;而在String.getBytes([encode])中,系統要作一個Unicode字符-->encode字符-->bytes的轉換。

 

但願經過本文的介紹可以使你對字符集編碼的概念以及Java與字符集編碼之間的關係有個清楚的認識,我相信,若是搞清楚了他們之間的關係,那個在Java world鼎鼎有名的亂碼問題將一去再也不復返了J

相關文章
相關標籤/搜索