七、編碼與編碼格式

一、ASCII碼

在計算機內(nèi)部,所有的信息最終都是一個二進制值,而每一個二進制位有0和1兩種狀態(tài),所以八個二進制位就可以表示出256中不同的狀態(tài),也就是說,一個字節(jié)可以表示256中不同的狀態(tài),每一個狀態(tài)對應(yīng)一個符號,就是256個符號。
上個世紀60年代,美國制定了一套字符編碼,對英語字符與二進制位之間的關(guān)系,做了統(tǒng)一規(guī)定。這被稱為 ASCII 碼,一直沿用至今。
ASCII碼規(guī)定了128個字符的編碼,這128個符號只占用一個字節(jié)的后面7位,最前面的一位統(tǒng)一規(guī)定為0。

二、非ASCII碼

英語用128個符號編碼就夠了,但是用來表示其他語言,128個符號是不夠的。比如,在法語中,字母上方有注音符號,它就無法用 ASCII 碼表示。于是,一些歐洲國家就決定,利用字節(jié)中閑置的最高位編入新的符號。比如,法語中的é的編碼為130(二進制10000010)。這樣一來,這些歐洲國家使用的編碼體系,可以表示最多256個符號。
但是,這里又出現(xiàn)了新的問題。不同的國家有不同的字母,因此,哪怕它們都使用256個符號的編碼方式,代表的字母卻不一樣。比如,130在法語編碼中代表了é,在希伯來語編碼中卻代表了字母Gimel (?),在俄語編碼中又會代表另一個符號。但是不管怎樣,所有這些編碼方式中,0--127表示的符號是一樣的,不一樣的只是128--255的這一段。

至于亞洲國家的文字,使用的符號就更多了,漢字就多達10萬左右。一個字節(jié)只能表示256種符號,肯定是不夠的,就必須使用多個字節(jié)表達一個符號。比如,簡體中文常見的編碼方式是 GB2312,使用兩個字節(jié)表示一個漢字,所以理論上最多可以表示 256 x 256 = 65536 個符號。

三、Unicode

世界上存在著多種編碼方式,同一個二進制數(shù)字可以被解釋成不同的符號。因此,要想打開一個文本文件,就必須知道它的編碼方式,否則用錯誤的編碼方式解讀,就會出現(xiàn)亂碼。
可以想象,如果有一種編碼,將世界上所有的符號都納入其中。每一個符號都給予一個獨一無二的編碼,那么亂碼問題就會消失。這就是 Unicode,就像它的名字都表示的,這是一種所有符號的編碼。
Unicode 當然是一個很大的集合,現(xiàn)在的規(guī)模可以容納100多萬個符號。每個符號的編碼都不一樣,比如,U+0639表示阿拉伯字母Ain,U+0041表示英語的大寫字母A,U+4E25表示漢字。具體的符號對應(yīng)表,可以查詢unicode.org,或者專門的漢字對應(yīng)表。
GBK和UTF-8都是用來序列化或存儲unicode編碼的數(shù)據(jù)的,但是分別是2種不同的格式; 他們倆除了格式不一樣之外,他們所關(guān)心的unicode編碼范圍也不一樣,utf-8考慮了很多種不同國家的字符,涵蓋整個unicode碼表,所以其存儲一個字符的編碼的時候,使用的字節(jié)長度也從1字節(jié)到4字節(jié)不等;而GBK只考慮中文——在unicode中的一小部分——的字符,的編碼,所以它算好了只要2個字節(jié)就能涵蓋到絕大多數(shù)常用中文(2個字節(jié)能表示6w多種字符),所以它存儲一個字符的時候,所用的字節(jié)長度是固定的;

四、Unicode的問題

最終Unicode的容量越來越大,其表示每一個符號的字節(jié)也越來越長。在這種情況下,如果用固定幾個字節(jié)來表示一個符號的話,則對于最開始的字節(jié)會出現(xiàn)空間浪費;如果用不定的字節(jié)來描述一個符號的話,那如何才能知道到底是用了幾個字節(jié)來表示一個符號?

五、解決辦法:UTF-8

UTF-8 就是在互聯(lián)網(wǎng)上使用最廣的一種 Unicode 的實現(xiàn)方式。其他實現(xiàn)方式還包括 UTF-16(字符用兩個字節(jié)或四個字節(jié)表示)和 UTF-32(字符用四個字節(jié)表示),不過在互聯(lián)網(wǎng)上基本不用。重復(fù)一遍,這里的關(guān)系是,UTF-8 是 Unicode 的實現(xiàn)方式之一。

UTF-8 最大的一個特點,就是它是一種變長的編碼方式。它可以使用1~4個字節(jié)表示一個符號,根據(jù)不同的符號而變化字節(jié)長度。

UTF-8 的編碼規(guī)則很簡單,只有二條:

1)對于單字節(jié)的符號,字節(jié)的第一位設(shè)為0,后面7位為這個符號的 Unicode 碼。因此對于英語字母,UTF-8 編碼和 ASCII 碼是相同的。

2)對于n字節(jié)的符號(n > 1),第一個字節(jié)的前n位都設(shè)為1,第n + 1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進制位,全部為這個符號的 Unicode 碼。

image.png

跟據(jù)上表,解讀 UTF-8 編碼非常簡單。如果一個字節(jié)的第一位是0,則這個字節(jié)單獨就是一個字符;如果第一位是1,則連續(xù)有多少個1,就表示當前字符占用多少個字節(jié)。

下面,還是以漢字嚴為例,演示如何實現(xiàn) UTF-8 編碼。

嚴的 Unicode 是4E25(100111000100101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800 - 0000 FFFF),因此嚴的 UTF-8 編碼需要三個字節(jié),即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,從嚴的最后一個二進制位開始,依次從后向前填入格式中的x,多出的位補0。這樣就得到了,嚴的 UTF-8 編碼是11100100 10111000 10100101,轉(zhuǎn)換成十六進制就是E4B8A5。

六、總結(jié)

應(yīng)該說,每一種字符集本身就代表著一種編碼格式,(unicode也不例外)所以當采用這些字符集的時候,就是說使用了這種編碼格式。

七、getbyte方法

在java中,getBytes()方法如果不指定字符集,則得到的是一個操作系統(tǒng)默認的編碼格式的字節(jié)數(shù)組;如果指定字符集,則得到的是在指定字符集下的字節(jié)數(shù)組
實例:

package ObjectRef;

import java.io.UnsupportedEncodingException;
/**
 * @author hankun
 * @create 2017-06-26 20:28
 */
public class Test4 {
    /**
     *
     * 1、Unicode是一種編碼規(guī)范,是為解決全球字符通用編碼而設(shè)計的,而UTF-8,UTF-16等是這種規(guī)范的一種實現(xiàn)。
     2、java內(nèi)部采用Unicode編碼規(guī)范,也就是支持多語言的,具體采用的UTF-16編碼方式。
     3、不管程序過程中用到了gbk,iso8859-1等格式,在存儲與傳遞的過程中實際傳遞的都是Unicode編碼的數(shù)據(jù),要想接收到的值不出現(xiàn)亂碼,就要保證傳過去的時候用的是A編碼,接收的時候也用A編碼來轉(zhuǎn)換接收。
     4、如果雙方的file.encoding確保都相同,那就省事了,都默認轉(zhuǎn)了,但往往在不同項目交互時很多時候是不一致的,這個時候是必須要進行編碼轉(zhuǎn)換的。
     5、無論如論轉(zhuǎn)換,java程序的數(shù)據(jù)都是要先和Unicode做轉(zhuǎn)換,這樣也就是能處理多語言字符集的原因了。底層保持了一致,只要在傳值和接值的時候也一致就肯定不會出現(xiàn)亂碼了。
     * */
    public static void main(String[] args) throws UnsupportedEncodingException {
        String str = "中文字符";
        System.out.println("original string---" + str);// 會正常輸出原始串
        /**
         *
         * str.getBytes();  如果括號中不寫charset,則采用的是Sytem.getProperty("file.encoding"),即當前文件的編碼方式,
         *
         * 很多人寫的是系統(tǒng)的默認編碼,通過代碼測試并非如此,實際得到的是文件的編碼方式*
         *
         * str.getBytes("charset");//指定charset,即將底層存儲的Unicode碼解析為charset編碼格式的字節(jié)數(shù)組方式
         *
         * String new_str=new String(str.getBytes("utf-8"),"gbk"));
         *
         * //將已經(jīng)解析出來的字節(jié)數(shù)據(jù)轉(zhuǎn)化為gbk編碼格式的字符串,在內(nèi)存中即為gbk格式的字節(jié)數(shù)組轉(zhuǎn)為Unicode去交互傳遞
         */
        String new_str = new String(str.getBytes("utf-8"), "gbk");
        /**
         *
         * 此時的輸出是亂碼,在UTF-8的file.encoding下輸出gbk格式的數(shù)據(jù)肯定是亂碼,但是new_str的確是gbk編碼式的
         *
         * 此時的亂碼源于encoding不符,但gbk格式的new_str本身數(shù)據(jù)并沒有問題,通過下面的轉(zhuǎn)換也可以看得出來
         */
        System.out.println("new string----" + new_str);
        String final_str = new String(new_str.getBytes("gbk"), "utf-8");// 此處的含意與最上邊的注釋是一致的參數(shù)含意
        /**
         *
         *輸出是正常的,此時將gbk編碼格式的new_str字符串,用gbk這個charset去解析它,然后用utf-8再轉(zhuǎn)碼一次,
         *
         * 因為new_str確實是gbk格式的,才能經(jīng)過utf-8編碼得到正常的數(shù)據(jù)顯示。
         */
        System.out.println("final string---" + final_str);
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容