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好文推荐

相关文章
相关标签/搜索