計算機編碼基礎知識及Java中編碼轉換


一、前言

最近項目中用到招商銀行的企業直連功能,通過接口的方式直接調用招商銀行的前置機(http://www.cmbchina.com/corporate/firmbank/FirmbankInfo.aspx?guid=d0917853-6256-44ea-b1e2-24b8524042db)而后進行轉賬和信息的查詢。但招商銀行的數據是通過gbk格式進行傳輸的,而我們的系統使用的utf8編碼格式,理所當然的就出現了亂碼問題。

借此機會,對編碼及Java中如何解決亂碼進行匯總。

如果只是想要知道如何解決該問題,可以直接閱讀4.2。

如果想對此有全面的了解,請接着向下看。

二、基礎知識

1.為什么要編碼

   大家都知道,計算機存儲數據是以0、1進行數據的存儲,而人類的語言則多種多樣,要想讓計算機能夠理解眾多的人類語言,就必須將人類語言“翻譯”成計算機能夠看懂的語言,這就是為什么要進行編碼。編碼的目的就是為了讓計算機能夠理解人類的語言。

2.編碼格式是什么?

   通俗的講編碼格式就是“翻譯”的規則,人類語言與計算機語言的對應規則。由於人類語言眾多,編碼格式的種類相應的也有多種。

3.為什么會出現亂碼問題?

   常見的亂碼問題就是因為沒有使用正確的編碼格式進行信息的轉換,導致獲得的信息看不懂。如同“計算機”這個詞,這是中文的表達方式,轉換成英文是“computer”,但如果你用日語的轉換規則進行翻譯就是“コンピュータ”,一個不懂日語的人肯定不知道這表示什么意思,就認為是亂碼了。

4.亂碼問題解決原則

   也就是說,解決亂碼問題的一個必要條件是需要對信息的原始編碼格式和信息的目標的編碼格式都了解。是不是知道信息的原始編碼格式和信息的目標的編碼格式就一定能解決亂碼問題?答案是否定的。有些詞在這個語言中有,而在另一種語言中的情況是存在的,對應到計算機的世界中也是如此,為了能夠確保“翻譯”正確,有時需要引入第三種編碼格式作為橋梁。

 

三、常用編碼規格

1.ASCII 碼

    學過計算機的人都知道 ASCII 碼,ASCII 碼是美國標准信息交換代碼(American Standard Code for Information Interchange)的縮寫, 為美國英語通信所設計。它由128個字符組成,包括大小寫字母、數字0-9、標點符號、非打印字符(換行符、制表符等4個)以及控制字符(退格、響鈴等)組成。 總共有 128 個,用一個字節的低 7 位表示,0~31 是控制字符如換行回車刪除等;32~126 是打印字符,可以通過鍵盤輸入並且能夠顯示出來。 例如'A'是65,'a'是97。

但是,由於它是針對英語設計的,當處理帶有音調標號(形如漢語的拼音)的歐洲文字時就會出現問題。

2.ISO-8859-1

   128 個字符顯然是不夠用的,於是 ISO 組織在 ASCII 碼基礎上又制定了一些列標准用來擴展 ASCII 編碼,它們是 ISO-8859-1~ISO-8859-15,其中 ISO-8859-1 涵蓋了大多數西歐語言字符,所有應用的最廣泛。ISO-8859-1 仍然是單字節編碼,它總共能表示 256 個字符。

3.GB2312

  它的全稱是《信息交換用漢字編碼字符集基本集》,它是雙字節編碼,總的編碼范圍是 A1-F7,其中從 A1-A9 是符號區,總共包含 682 個符號,從 B0-F7 是漢字區,包含 6763 個漢字:其中一級漢字3755個,二級漢字3008個;同時,GB2312收錄了包括拉丁字母、希臘字母、日文平假名及片假名字母、俄語西里爾字母在內的682個全角字符。
GB2312基本滿足了漢字的計算機處理需要,它所收錄的漢字已經覆蓋中國大陸99.75%的使用頻率。

4.GBK

  GBK是漢字編碼標准之一,全稱《漢字內碼擴展規范》(GBK即“國標”、“擴展”漢語拼音的第一個字母,英文名稱:Chinese Internal Code Specification)。GBK編碼,是在GB2312-80標准基礎上的內碼擴展規范,使用了雙字節編碼方案,其編碼范圍從8140至FEFE(剔除xx7F),共23940個碼位,共收錄了21003個漢字,完全兼容GB2312-80標准,支持國際標准ISO/IEC10646-1和國家標准GB13000-1中的全部中日韓漢字,並包含了BIG5編碼中的所有漢字。GBK 向下與 GB 2312 編碼兼容,向上支持 ISO 10646.1 國際標准,是前者向后者過渡過程中的一個承上啟下的標准。

5.Unicode

   Unicode(Universal Code 統一碼)是基於通用字符集(Universal Character Set)的標准來發展, Unicode是國際組織制定的可以容納世界上所有文字和符號的字符編碼方案。它通過增加一個高字節對ISO Latin-1字符集進行擴展,當這些高字節位為0時,低字節就是ISO Latin-1字符。UNICODE支持歐洲、非洲、中東、亞洲(包括統一標准的東亞象形漢字和韓國象形文字)。但是,UNICODE並沒有提供對諸如Braille, Cherokee, Ethiopic, Khmer, Mongolian, Hmong, Tai Lu, Tai Mau文字的支持。同時它也不支持如Ahom, Akkadian, Aramaic, Babylonian Cuneiform, Balti, Brahmi, Etruscan, Hittite, Javanese, Numidian, Old Persian Cuneiform, Syrian之類的古老文字。

    事實證明,對可以用ASCII表示的字符使用UNICODE並不高效,因為UNICODE比ASCII占用大一倍的空間,而對ASCII來說高字節的0對他毫無用處。為了解決這個問題,就出現了一些中間格式的字符集,他們被稱為通用轉換格式,即UTF(Universal Transformation Format)。

unicode編碼規范中常用的是utf-16和utf-8。

6.UTF-16

UTF-16 用兩個字節來表示 Unicode 轉化格式,這個是定長的表示方法,不論什么字符都可以用兩個字節表示,兩個字節是 16 個 bit,所以叫 UTF-16。UTF-16 表示字符非常方便,每兩個字節表示一個字符,這個在字符串操作時就大大簡化了操作,這也是 Java 以 UTF-16 作為內存的字符存儲格式的一個很重要的原因。

UTF-16 統一采用兩個字節表示一個字符,雖然在表示上非常簡單方便,但是也有其缺點,有很大一部分字符用一個字節就可以表示的現在要兩個字節表示,存儲空間放大了一倍,在現在的網絡帶寬還非常有限的今天,這樣會增大網絡傳輸的流量,而且也沒必要。

7.UTF-8

UTF-8 采用了一種變長技術,每個編碼區域有不同的字碼長度。UTF-8用1到6個字節編碼UNICODE字符。如果UNICODE字符由2個字節表示,則編碼成UTF-8很可能需要3個字節。而如果UNICODE字符由4個字節表示,則編碼成UTF-8可能需要6個字節。用4個或6個字節去編碼一個UNICODE字符可能太多了,但很少會遇到那樣的UNICODE字符。UTF-8 可以在同一個頁面顯示中文簡體繁體及其它語言(如日文,韓文) 。

實際表示ASCII字符的UNICODE字符,將會編碼成1個字節,並且UTF-8表示與ASCII字符表示是一樣的。所有其他的UNICODE字符轉化成UTF-8將需要至少2個字節。

小結:

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

三、Java編碼相關知識

1. byte(字節)和char(字符)

兩個基本概念

字符:人類使用的記號,抽象意義上的一個符號。比如‘中’、‘x’。

字節:計算機中存儲數據的單元,一個8位的二進制數,是一個很具體的存儲空間。

在Java中byte和char都是基本數據類型:byte(字節)占8位(8 bit),它的值域被定義為-128~127。char(字符)為兩個字節(2 bytes)。

在進行數據存儲和傳輸時必然會涉及到字節和字符間的轉換。字符和字節的對應關系因編碼格式的不同而有所變化,具體見下面的示例。

需要和Java中的byte和char基本數據類型區分開,byte(字節)占8位(8 bit),它的值域被定義為-128~127。char(字符)為兩個字節(2 bytes)。char和byte的對應關系是固定的。

Java語言中基本類型所占存儲空間的大小是固定的,它們的大小並不像其它大多數語言那么隨機器硬件架構的變化而變化。這種所占存存儲空間大小的不變性是java程序具有可移值性的原因之一。

2.編碼示例

前面講的都是基礎知識,現在用一個示例直觀的了解下同一個字符串在不同的編碼格式下的編碼結果。

 
public static void getBytesTest() {
    String name = "xjp zx";//(前三個字符是最新的主席的名字,但博客園里無法發表,請自行替換成漢字)
    System.out.print("原始字符: ");
    for (char b : name.toCharArray()) {
        System.out.print(b + " ");
    }
    System.out.println();
    try {
        byte[] iso8859 = name.getBytes("ISO-8859-1");
        printByte("ISO-8859-1",iso8859);
 
        byte[] gb2312 = name.getBytes("GB2312");
        printByte("GB2312",gb2312);
 
        byte[] gbk = name.getBytes("GBK");
        printByte("GBK",gbk);
 
        byte[] utf16 = name.getBytes("UTF-16");
        printByte("UTF-16",utf16);
 
        byte[] utf8 = name.getBytes("UTF-8");
        printByte("UTF-8",utf8);
 
    } catch (UnsupportedEncodingException e) {
        e.printStackTrace();
    }
}
 
public static void printByte(String charsetName,byte[] bytes) {
    System.out.print("編碼格式為 " +charsetName +" 時的bytes值: ");
    for (byte b : bytes) {
        System.out.print(b + " ");
    }
    System.out.println();
}
 
代碼的默認編碼是gbk格式的.程序執行的結果如下:
image
從執行結果可以看出:
1)英文字母在各種編碼格式中均是一個byte,且對應的byte值均相同.
2)ISO-8859-1編碼格式下一個字符為一個byte
3)GB2312和GBK的編碼結果基本上相同
4)UTF-16編碼格式每一個字符均是兩個byte
5)UTF-8編碼格式下一個中文字符是3個byte,英文字符是1個byte
 

四、Java中編碼轉換

1.基本原理

通過上面的示例可以知道,同一個中文字符在不同的編碼格式下會有不同的byte值,如果想進行正確的轉碼,就需要得到對應的正確的byte值,這個可以通過String類的getBytes(String charsetName)方法獲得。在獲得正的byte值后利用String類的String(byte bytes[], String charsetName)構造函數重新生成一個新的String對象即可。

2.轉換示例代碼

下面的示例描述了將一個gbk編碼的字符串轉為utf-8格式,再將utf-8格式的字符串轉為gbk格式。

 
public static void doubleTranslate() throws UnsupportedEncodingException {
    String gbk = "業務參考號重復";
    System.out.print("GBK格式下的bytes: ");
    for (byte b : gbk.getBytes("GBK")) {
        System.out.print(b+" ");
    }
    System.out.println();
    System.out.print("UTF-8格式下的bytes: ");
    String utf8 = new String(gbk.getBytes("UTF-8"),"UTF-8"); //轉換為UTF-8
    for (byte b : utf8.getBytes("UTF-8")) {
        System.out.print(b+" ");
    }
    System.out.println();
    System.out.println("gbk轉為utf-8: "+utf8);
    System.out.println("utf-8轉為gbk: "+new String(utf8.getBytes("GBK"),"GBK"));
}
 
代碼執行結果為:
image
 
 

 

五、總結及進一步的工作

1.小結:

本文首先對編碼知識進行了簡要介紹,而后通過示例展示了同一個字符串在不同編碼格式下的byte值,而后通過示例演示了在Java中如何進行編碼格式的轉換。

轉換成功的前提是獲得要轉換編碼格式的byte值,而后利用String類的構造函數即可。

2.進一步的工作

本文只是說明了怎么做,但是對其背后的實現原理沒有進行介紹。知其然,知其所以然,才是好的學習習慣。接下來會另起一篇介紹轉碼后面的實現機理。

 

參考資料:

http://www.blogjava.net/liuyz2006/articles/385768.html

http://blog.csdn.net/baijinwen/article/details/1623544

百度百科及wiki百科


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM