Unicode再探

簡介

爲何須要編碼呢?咱們知道計算機內部使用高低電位表示0,1經過0,1組合來表示數字,可是咱們看到的都是圖形,不論是圖片,英文字母,中文文字,數字都是圖形。咱們之因此看見圖形,是由於計算機的顯示系統把計算機內部的0,1轉換成了圖形,而後經過像電子槍這樣的工具把圖形繪製到屏幕上。可是0,1和圖形之間的對應關係應該是怎麼的呢?這顯然須要一個你們都遵照的規則來轉換,這個規則就是編碼。就是數字和圖形的對應關係。例如,ASCII碼中就使用48這個數字來表示字符(圖形)"0"。在屏幕上咱們看到的是"0",可是在計算機內部存儲的倒是110000(高低電位)這樣的二進制。 那麼問題又來了,剛剛開始的時候的統一是有侷限的啊,好比ASCII只規定了字符"a"的編碼是1100001,可是中國人要使用的文字,好比說"中"字,該存儲爲何呢?因此中國人就本身弄了本身的字符集編碼GB2312,GB18030等。固然要利用這些編碼是須要圖形系統的支持,就好比說"中"字,我隨便弄一個編碼,那也得圖形可以繪製"中"這個圖形對吧? 如今不少國家都有本身的字符集編碼了,可是問題又來了,如今都流行國際化的套路了,的跟上時代的腳步啊,這樣你有你的編碼,我有個人編碼不統一就是最大的問題,由於一樣的一個編碼xxxxxxxx在不一樣的編碼系統中可能對應的是不一樣的圖形,這顯然是不利於交流的,因此就有了Unicode。若是你們都使用Unicode那麼數字和圖形就是一對一的關係。 Unicode包含更大的字符集(圖形),也就意味着要使用更多的0,1來區分編碼(更多的字節)。這樣有的人確定就不肯意了,好比說老美,老美確定會想原本我原本1個字節能搞定的事情爲何要用2字節或者4字節。Unicode的設計者顯然也考慮到了這樣的問題,因此把Unicode的字符集設計的很巧妙,只須要一些特殊的轉換方式就能避免這樣的浪費。這些巧妙的轉換方式就包括了UTF-8這樣的編碼方式。html

##幾個基本概念java

Unicode

Unicode是一種字符編碼方法,不過它是由國際組織設計,能夠容納全世界全部語言文字的編碼方案,也是一個字符集。Unicode只是規定如何編碼,並無規定如何傳輸、保存這個編碼。 Unicode的學名是"Universal Multiple-Octet Coded Character Set",簡稱爲UCS。UCS能夠看做是"Unicode Character Set"的縮寫。從這個名稱中咱們也能夠看出Unicode也是一個字符集。UCS又分爲UCS-2和UCS-4。git

UCS-2

UCS-2就是用兩個字節編碼,UCS-2有2^16=65536個碼位數據庫

UCS-4

UCS-4就是用4個字節(實際上只用了31位,最高位必須爲0)編碼,UCS-4有2^31=2147483648個碼位。 UCS-4根據最高位爲0的最高字節分紅2^7=128個group(組)。每一個group再根據次高字節分爲256個plane(平面)。每一個plane根據第3個字節分爲256 rows(行),每行包含256個cells(單元)。固然同一行的cells只是最後一個字節不一樣,其他都相同。 group 0的plane 0被稱做Basic Multilingual Plane, 即BMP。或者說UCS-4中,高兩個字節爲0的碼位被稱做BMP。 Unicode標準計劃使用group 0 的17個平面: 從平面0到平面16,即數字0-0x10FFFF。 每一個平面有2^16=65536個碼位。Unicode計劃使用了17個平面,一共有 1765536=1114112(10FFFF)個碼位。其實,如今已定義的碼位只有238605個,分佈在平面0、平面一、平面二、平面1四、平面1五、平面16。其中平面15和平面16上只是定義了兩個各佔65534個碼位的專用區(Private Use Area),分別是0xF0000-0xFFFFD和0x100000-0x10FFFD。所謂專用區,就是保留給你們放自定義字符的區域,能夠簡寫爲 PUA。平面0也有一個專用區:0xE000-0xF8FF,有6400個碼位。平面0的0xD800-0xDFFF,共2048個碼位,是一個被稱做代理區(Surrogate)的特殊區域。238605-655342-6400-2408=99089。餘下的99089個已定義碼位分佈在平面0、平面 一、平面2和平面14上,它們對應着Unicode目前定義的99089個字符,其中包括71226個漢字。平面0、平面一、平面2和平面14上分別定義 了52080、341九、43253和337個字符。平面2的43253個字符都是漢字。平面0上定義了27973個漢字。windows

BMP

將UCS-4的BMP去掉前面的兩個零字節就獲得了UCS-2。在UCS-2的兩個字節前加上兩個零字節,就獲得了UCS-4的BMP。而目前的UCS-4規範中尚未任何字符被分配在BMP以外。編輯器

UTF

UTF是"UCS Transformation Format"的縮寫。UCS表示字符集,是字符和數字編碼的對應集合。UTF是從字面意思來看就是UCS字符集的轉換格式,就是UCS字符集編碼的一種轉換的編碼方式,Unicode已經有數字編碼了,爲何要轉換格式呢?答案是爲了節約存儲空間和提升傳輸效率。就以UTF8爲例,編碼一個ascii字符UTF8使用1個字節,若是不使用任何技巧,直接存Unicode碼,USC-2須要2個字節,USC-4須要4個字節。既然有的節省了空間,那麼確定就有須要花跟多空間的,不過UTF轉換以後的通常狀況下是可以節省更多空間的。因此爲了兼容和國際化咱們使用Unicode字符集,爲了節省空間咱們使用UTF編碼。後面會詳細介紹幾個重要的UTF編碼方式。工具

UCD

UCD是Unicode字符數據庫(Unicode Character Database)的縮寫。UCD由一些描述Unicode字符屬性和內部關係的純文本或html文件組成。測試

Block

Block是Unicode字符的一個屬性。屬於同一個Block的字符有着相近的用途。Block表中的開始碼 位、結束碼位只是用來劃分出一塊區域,在開始碼位和結束碼位之間可能還有不少未定義的碼位。編碼

Script

Unicode中每一個字符都有一個Script屬性,這個屬性代表字符所屬的文字系統。有兩個Script值有着特殊的含義: Common:Script屬性爲Common的字符可能在多個文字系統中使用,不是某個文字系統特有的。例如:空格、數字等。 Inherited:Script屬性爲Inherited的字符會繼承前一個字符的Script屬性。主要是一些組合用符號,例如:在「組合附加符號」區(0x300-0x36f),字符的Script屬性都是Inherited。設計

##UTF編碼方式

UTF-8

UCS-4 range (hex.)           UTF-8 octet sequence (binary)
   0000 0000-0000 007F   0xxxxxxx
   0000 0080-0000 07FF   110xxxxx 10xxxxxx
   0000 0800-0000 FFFF   1110xxxx 10xxxxxx 10xxxxxx
   0001 0000-001F FFFF   11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
   0020 0000-03FF FFFF   111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
   0400 0000-7FFF FFFF   1111110x 10xxxxxx ... 10xxxxxx

UTF-8的特色是對不一樣範圍的字符使用不一樣長度的編碼。對於0x00-0x7F之間的字符,UTF-8編碼與 ASCII編碼徹底相同。UTF-8編碼的最大長度是6個字節。可能其餘的一下資料第4字節模板爲010000-10FFFF,而且最多就是4個字節,是由於Unicode定義使用的基本只有17個平面,前面已經提到過17個平面每個平面有65536個碼位,因此總共有65536*17=1114112個碼位,因此用010000-10FFFF表示便可。 下面經過一個具體的例子來講明一下UTF-8是怎樣對Unicode進行編碼(轉碼)的。 咱們在windows下用記事本打開輸入一個漢字"中",而後使用unicode的編碼保存,咱們用16進制編輯器打開發現內容爲FF FE 2D 4E。記事本爲內容加了2個字節FF FE表示字節序,表示使用的是小端機的保存模式。就是說明漢字"中"的實際的Unicode編碼爲4E 2D。 "中"字的Unicode編碼是0x4E2D。0x4E2D在0x0800-0xFFFF之間,使用用3字節模板了: 1110xxxx 10xxxxxx 10xxxxxx。 將0x4E2D轉換爲二進制是:0100 1110 0010 1101 注意轉換過程當中高位補0,最後截取低位的x位數字,x的數量就是,上面模板中對應範圍x的數量,好比0x0800-0xFFFF範圍對應模板有16個x,就截取低16位。而後用這個比特流依次代替模板中的x,獲得: 11100100 10111000 10101101,即"中"的UTF-8編碼爲:E4 B8 AD UTF-8爲何編碼最大長度爲6字節呢?這顯然是精心設計過的,6字節模板中x的位數爲31位,恰好能夠把USC-4表示完。每個範圍也是計算過的,轉換的二進制位數,絕對不會超過模板中x的位數。

UTF-16

UTF-16編碼不是隻使用16位來編碼,而是以16位無符號整數爲單位進行編碼。咱們把Unicode編碼記做U。編碼規則以下:

  1. 若是U<0x10000(65536),U的UTF-16編碼就是U對應的16位無符號整數
  2. 若是U≥0x10000(65536),咱們先計算U=U-0x10000(65536)而後將U寫成二進制形式:

yyyy yyyy yyxx xxxx xxxx, U的UTF-16編碼(二進制)就是: 110110yyyyyyyyyy 110111xxxxxxxxxx。

爲何U(Unicode的編碼值)能夠被寫成20個二進制位呢?在Unicode如今定義最經常使用的的17個平面中的最大碼位是0x10ffff(1114111),減去0x10000後,U的最大值是0xfffff,每個16進製爲可使用4個二進制位表示,因此確定能夠用20個二進制位表示。 咱們仍是使用"中"字爲例,來講明UTF-16編碼方式: "中"字的Unicode編碼是0x4E2D,0x4E2D小於0x10000,因此UTF-16直接使用0x4E2D就能夠了。 對於U≥0x10000的字符咱們基本用不到,我嘗試了也沒有找到打印的方法,若是感興趣的同窗能夠嘗試找一些這樣的字符打印一下,例如:左邊一個"口",右邊一個"才"這個字(UTF-16 0xD842,0xDFB9),又例如:左邊一個"口",右邊一個"乙"這個字(UTF-16 0xD842,0xDF99)。 對於Java對於非BMP平面的字符,輸出的也不是字符自己,而是字符的代理對(下面介紹)的16進制編碼。

代理區(Surrogate)

UTF-16編碼對於大於65536(2^16)顯然是不能用1個16位無符號的整數表示了,因此使用了2個16位無符號的整數表示,根據UTF-16編碼的規則, 第1個16位模板是這樣的:

110110 yyyyyyyyyy 這種模板的範圍顯然是: 110110 0000000000 - 110110 1111111111 (0xD800-0xDBFF)

第2個16位的模板是這樣的:

110111 xxxxxxxxxx 這種模板的範圍顯然是: 110111 0000000000 - 110111 1111111111 (0xDC00-0xDFFF)

而後弄Unicode的那一幫人給(0xD800-0xDFFF)這個合起來的範圍起了一個名字叫作代理區(Surrogate), (0xD800-0xDBFF)叫作高位代理,叫作低位代理 其中(0xDB80-0xDBFF)這個區域又被稱爲高位專用代理,這是由於把(0xDB80-0xDBFF)按照UTF-8的編碼方式反編碼回去得到的Unicode編碼恰好在15,16這2個專用自定義區(Private User Area)

UTF-32

UTF-32編碼最簡單,由於UCS最多使用4個字節,因此UTF-32編碼以32位無符號整數爲單位,Unicode的UTF-32編碼就是Unicode碼值對應的32位無符號整數。

Java Character的一些測試

import org.junit.Test;

public class UTest {

    @Test
    public void testU() {
        char s = '圝';
        System.out.println((int) s);
        // System.out.println((char)(100000));//大於65535的數據被截斷了
        System.out.println(Character.MAX_CODE_POINT);
        char c = Character.forDigit(14, 16);
        System.out.println(c);
        //輸出指定位置上的unicode的字符,若是不是BMP(簡單來講BMP就是unicode<65536)
        //就輸出的是UTF-16編碼的代理對,2個無符號16位
        char[] chars = Character.toChars(20013);// 4E2D 中
        for (char ch : chars)
            System.out.println(ch);

        chars = Character.toChars(134192);// 0x20C30
        for (char ch : chars) {
            int up = (int) ch;
            System.out.println(Integer.toHexString(up));
        }
        //獲取低位代理
        char low = Character.lowSurrogate(0x20C30);
        System.out.println(Integer.toHexString((int) low));
        //獲取高位代理
        char high = Character.highSurrogate(0x20C30);
        System.out.println(Integer.toHexString((int) high));

        //知道字符代理對,計算字符unicode編碼中的位置
        int codePointAt = Character.codePointAt(intToCharArray(0xD842, 0xDFB9), 0);
        System.out.println(codePointAt);

    }

    public static byte[] charToByte(char c) {
        byte[] b = new byte[2];
        b[0] = (byte) ((c & 0xFF00) >> 8);
        b[1] = (byte) (c & 0xFF);
        return b;
    }

    public static char[] intToCharArray(int high, int low) {
        checkSurrogate(high, low);
        char[] result = new char[2];
        result[0] = (char) high;
        result[1] = (char) low;
        return result;
    }
    
    /**
     * 根據UTF-16編碼規則,高位代理範圍爲:
     * 11011000 00000000到11011011 11111111,即0xD800-0xDBFF
     * 低位代理範圍爲:
     * 11011100 00000000到11011111 11111111,即0xDC00-0xDFFF
     * @param high
     * @param low
     * @return
     */
    private static void checkSurrogate(int high,int low)
    {
        if(0xD800>high || high>0xDBFF)
            throw new IllegalArgumentException("非法的高位代理:"+high);
        if(0xDC00>low || low>0xDFFF)
            throw new IllegalArgumentException("非法的低位代理:"+low);
    }

}

Unicode好文推薦

相關文章
相關標籤/搜索