java中string的不可不知的編碼知識

今天要分享的內容是java的string的字符串編碼

首先記住一句話,字符串在 java 內存中總是按 unicode 編碼存儲的

先看一個測試用例

/**
 * @author hankun
 * @create 2017-06-27 14:24
 */
public class unicode {

    /**
     * 字符串轉換unicode
     */
    public static String string2Unicode(String string) {

        StringBuffer unicode = new StringBuffer();
        for (int i = 0; i < string.length(); i++) {

            // 取出每一個字符
            char c = string.charAt(i);

            // 轉換爲unicode
            unicode.append("\\u" + Integer.toHexString(c));
        }

        return unicode.toString();
    }


    /**
     * unicode 轉字符串
     */
    public static String unicode2String(String unicode) {

        StringBuffer string = new StringBuffer();

        String[] hex = unicode.split("\\\\u");

        for (int i = 1; i < hex.length; i++) {

            // 轉換出每一個代碼點
            int data = Integer.parseInt(hex[i], 16);

            // 追加成string
            string.append((char) data);
        }

        return string.toString();
    }

    /**
     * 測試字符串長度
     */
    public static void stringLength(String string) {

        System.out.println("String is = " + string+" ,default length = "+ string.length());
        System.out.println("String is = " + string+" ,getBytes length = "+ string.getBytes().length);
        System.out.println("String 中包含 " + (string.getBytes().length-string.length())+" 箇中文");

    }

    public static void main(String[] args) {
        String test = "中文ab";

        String unicode = string2Unicode(test);

        String string = unicode2String(unicode) ;

        System.out.println(unicode);

        System.out.println(string);
        String test1 = "中文";
        String test2 = "ab";
        stringLength(test);
        stringLength(test1);
        stringLength(test2);
    }
}

上面這個例子裏面,還提供了一個,快速判斷,字符串中,包含的中文個數方法

解釋

首先,Java中的一個char是2個字節。java採用unicode,2個字節來表示一個字符,這點與C語言中不同,c語言中採用ASCII,在大多數系統中,一個char通常佔1個字節,但是在0~127整數之間的字符映射,unicode向下兼容ASCII。而Java採用unicode來表示字符,一箇中文或英文字符的unicode編碼都佔2個字節,但如果採用其他編碼方式,一個字符佔用的字節數則各不相同。

在 GB 2312 編碼或 GBK 編碼中,一個英文字母字符存儲需要1個字節,一個漢字字符存儲需要2個字節。 在UTF-8編碼中,一個英文字母字符存儲需要1個字節,一個漢字字符儲存需要3到4個字節。在UTF-16編碼中,一個英文字母字符存儲需要2個字節,一個漢字字符儲存需要3到4個字節(Unicode擴展區的一些漢字存儲需要4個字節)。在UTF-32編碼中,世界上任何字符的存儲都需要4個字節。

   我的系統的默認編碼方式爲GBK,因此對於字符串 “中文ab”,
    如果調用length()方法返回其長度,得到的結果將爲:4。該方法返回的是字符串的字符數,無論是中文字符還是英文字符,都被看做是一個字符。
    如果將其轉換爲byte數組,而後返回byte數組的長度,得到的結果將爲:6。因爲在GBK編碼中,中文佔2個字節,而英文字符佔1個字節。

科普

字符:人們使用的記號,抽象意義上的一個符號。比如:‘1’,‘中’,‘a’

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

字符集:使用哪些字符。也就是說哪些漢字,字母和符號會被收入標準中。所包含“字符”的集合就叫做“字符集”。

編碼:規定每個“字符”分別用一個字節還是多個字節存儲,用哪些字節來存儲,這個規定就叫做“編碼”

平常我們所說的“字符集”,比如:GB2312, GBK, JIS 等,除了有“字符的集合”這層含義外,同時也包含了“編碼”的含義。

iso8859-1

屬於單字節編碼,最多能表示的字符範圍是 0-255,應用於英文系列。比如,字母 ‘a’ 的編碼爲0x61=97。 很明顯,iso8859-1 編碼表示的字符範圍很窄,無法表示中文字符。但是,由於是單字節編碼,和計算機最基礎的表示單位一致,所以很多時候,仍舊使用 iso8859-1 編碼來表示。而且在很多協議上,默認使用該編碼。比如,雖然”中文”兩個字不存在 iso8859-1 編碼,以 gb2312 編碼爲例,應該是”d6d0 cec4” 兩個字符,使用 iso8859-1 編碼的時候則將它拆開爲 4 個字節來表示:”d6 d0 ce c4”(事實上,在進行存儲的時候,也是以字節爲單位處理的)。而如果是 UTF 編碼,則是 6 個字節 “e4 b8 ad e6 96 87”。很明顯,這種表示方法還需要以另一種編碼爲基礎。(unicode)

GB2312/GBK

這是漢字的國標碼,專門用來表示漢字,是雙字節編碼,而英文字母和 iso8859-1 一致(兼容iso8859-1 編碼)。其中 gbk 編碼能夠用來同時表示繁體字和簡體字,而 gb2312 只能表示簡體字,gbk是兼容gb2312 編碼的。

Unicode

這是最統一的編碼,可以用來表示所有語言的字符,而且是定長雙字節(也有四字節的)編碼,包括英文字母在內。所以可以說它是不兼容 iso8859-1 編碼的,也不兼容任何編碼。不過,相對於iso8859-1 編碼來說,unicode 編碼只是在前面增加了一個 0 字節,比如字母 ‘a’ 爲 “00 61”。 需要說明的是,定長編碼便於計算機處理(注意 GB2312/GBK 不是定長編碼),而 unicode 又可以用來表示所有字符,所以在很多軟件內部是使用 unicode 編碼來處理的,比如 java。

UTF

考慮到 unicode 編碼不兼容 iso8859-1 編碼,而且容易佔用更多的空間:因爲對於英文字母,unicode 也需要兩個字節來表示。所以 unicode 不便於傳輸和存儲。因此而產生了 utf 編碼,utf 編碼兼容 iso8859-1 編碼,同時也可以用來表示所有語言的字符,不過,utf 編碼是不定長編碼,每一個字符的長度從1-6個字節不等。另外,utf 編碼自帶簡單的校驗功能。一般來講,英文字母都是用一個字節表示,而漢字使用三個字節。注意,雖然說utf是爲了使用更少的空間而使用的,但那只是相對於 unicode編碼來說,如果已經知道是漢字,則使用 GB2312/GBK 無疑是最節省的。不過另一方面,值得說明的是,雖然 utf 編碼對漢字使用3個字節,但即使對於漢字網頁,utf 編碼也會比 unicode 編碼節省,因爲網頁中包含了很多的英文字符。

getBytes(charset)

這是 java 字符串處理的一個標準函數,其作用是將字符串所表示的字符按照 charset 編碼,並以字節方式表示。
注意字符串在 java 內存中總是按 unicode 編碼存儲的。
當Java程序從輸入流、文件或字符文字量等途徑獲得字符串時,均會做字符編碼的轉換,例如InputStreamReader 的構造函數中就需要指定編碼方式,而對於從文件和字符文字量中獲得字符串時,均採用系統默認的編碼方式對字符數據進行解碼。

考慮下面一段代碼:

   String str=”中”;
   ① byte[] bytes = str.getBytes();
   ② bytes = str.getBytes(“ISO-8859-1”);

語句①:將一個只含有一個字符“中”的字符串文字量賦給 String 類的一個對象 str,字符文字量“中”是按照操作系統默認編碼方式進行編碼,在中文 windows 系統中通常是“GBK”,“中”在GBK編碼中是0xD6D0,在將該字符賦給str時,Java會對該字符串進行編碼轉換,即將GBK編碼方式的“中”轉換成Unicode編碼方式的“中”,Unicode編碼方式“中”的編碼是0x4E2D,所以str在程序運行期間在內存中的二進制表示成16進制就是0x4E2D。

語句②:獲得str字符串的二進制形式。getBytes(String encoding)方法需要指定編碼方式,表示獲得該字符串在何種編碼方式中的二進制形式。此語句中沒有設置參數,表示採用操作系統默認的編碼方式,即此處獲得的bytes是“中”在GBK編碼中的二進制形式,即bytes[0]=0xD6, bytes[1]=0xD0。

語句③:該語句與語句②的區別就是指定了編碼方式,此處指定的是ISO-8859-1,即通常所說的Latin-1,該編碼採用8bit對字符編碼,所以編碼空間中只有256個字符。該編碼中只包含了基本的ASCII碼和一些擴展的其它西歐字符,所以該字符集中不可能包含中文的“中”字,也就是說Java虛擬機無法在ISO-8859-1編碼集中找到“中”字對應的編碼,針對這種情況,就只返回一個問號(?,0x3f)字符,所以此時bytes.length只有1,且bytes[0]=0x3f。

new String(byte[] bytes, String encoding)

getBytes()方法從字符串獲得二進制的字節數組。如果要從二進制的字節數組獲得字符串,則就需要使用new String(byte[] bytes, String encoding)方法,該方法按照encoding編碼方法對字節數組bytes中的二進制數組進行解析,生成一個新的字符串對象。

  byte[] bytes = {(byte)0xD6, (byte)0xD0, (byte)0x31};
  ① String str = new String(bytes);
  ② str = new String(bytes,”ISO-8859-1”);

語句①:定義一個字節數組。

語句②:將該字節數組中的二進制數據按照默認的編碼方式(GBK)編碼成字符串,我們知道GBK中0xD6 0xD0表示“中”,0x31表示字符“1”(GBK兼容ASCII,但不兼容ISO-8859-1除ASCII之外的部分),所以str得到的值是“中1”。

語句③:該句用ISO-8859-1編碼方式對該字節數據進行編碼,由於在ISO-8859-1編碼方式中一個字節會被解析成一個字符,所以該字節數組會被解釋成包含三個字符的字符串,但由於在ISO-8859-1編碼方式中沒有對應0xD6和0xD0的字符,所以前兩個字符會產生兩個問號,由於0x31在ISO-8859-1編碼中對應字符“1”(ISO-8859-1也兼容ASCII),所以此語句得到str的值是“??1”。

最後

我相信看了,上面這麼些背景知識,是不是以前,不清楚的東西,都有寫明白了?多看幾次,就更能理解了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章