代码点和代码单元

来源:互联网 发布:海康威视 算法研究院 编辑:程序博客网 时间:2024/05/16 05:45

在core java碰到这个概念,讲述的有一点模糊,下面我们来解析一下这个问题,首先来看一下Unicode的相关知识。

1 Unicode

Unicode的官方称呼为“Universal Multiple-Ocet Coded Character Set”,简称UCS,俗称:UNICODE。
Unicode源于一个很简单的想法:将全世界所有的字符包含在一个集合里面,计算机只需要只要支持这一个字符集就可以显示所有的字符,进而就不会有乱码产生。它从0开始为每一个字符指定一个编号,这叫做"码点"(code point)。比如:码点0的符号就是null,即U+0000 = null。U+后面的十六进制数是Unicode的码点。
目前,最新版本为7.0版本,共计109449个字符,其中CJK(双字节)字符占74500个。也就是说中日韩文字为74500个,可以近似的理解为全世界三分之二以上的文字来自于东亚。如此多的符号,Unicode并不是一次性定义的,而是分区定义。其中每个区65536个字符,称为一个平面(plan)。目前一共有17个(2的5次方)个平面,也就是说整个Unicode字符集的大小心在是2的21次方。
其中,最前面的65535个字符被称为基本平面(BMP),其码值范围为:U+0000到U+FFFF。所有最常见的字符都被放在这个平面,当然,这个平面也是Unicode最新被定义的平面。剩下的字符都被放在辅助平面(SMP),码点范围为:U+010000到U+10FFFF。

2、编码方式

 Unicode只是规定了每个字符的码点,到底用什么样的字节序来表示这个码点,就涉及到编码方法了。最直观的编码方式就是每个码点使用四个字节表示,字节内容一一对应码点,这种编码方式就叫UTF-32。比如:码点0就用四个字节的0表示--U+0000 = 0x0000 0000;码点597D就在前面加两个字节的0表示--U+597D = 0x0000 597D。UTF-32的优点是转换规则简单,查找效率高,缺点是浪费空间,会比ASCLL编码大四倍,这个缺点导致基本没人使用这种编码方式。
       人们需要的是一种节约空间的编码方式,于是UTF-8应运而生。它是一种变长编码方式,字符长度从一个字节到四个字节不等,越是常用的字符,字节越短,最前面的128个字符,只是用1个字符来表示,与ASCLL码完全相同。UTF-8的编码规则很简单,只有一下两条:
1、对于单字节的符号,字节的第一位设为0,后面的7位为这个符号的Unicode码。因此对于英文字母,UTF-8编码和ASCLL编码是相同的。
2、对于n字节(n>1),第一个字节的前n位都设为1,第n+1位设为0,后面的字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
下表总结了编码规则,字母x表示可用于编码的位。

跟据上表,解读UTF-8编码非常简单。如果一个字节的第一位是0,则这个字节单独就是一个字符;如果第一位是1,则连续有多少个1,就表示当前字符占用多少个字节。
UTF-16编码介于UTF-32与UTF-8之间,同时结合了定长和变长两种编码方法的特点。它的编码规则很简单:基本平面的字符占用2个字节,辅助平面的字符占用4个字节。也就是说,UTF-16的编码长度要么是2个字节(U+0000到U+FFFF),要么是4个字节(U+010000到U+10FFFF)。于是问题来了,如果我们遇到两个字节,怎么看出它是一个字符还是需要跟其他两个字节结合放在一起解读?有一个很巧妙的解决方案是:从U+D800到U+DFFF是一个空段,即这些代码点不对应任何字符,因此可以用这个空段来映射辅助平面的字符。
具体来说,辅助平面的字符(编号大于U+FFFF)共有2的20次方,也就是说对应这些字符至少需要20个二进制位,UTF-16将这20位拆成两段,前10位映射在U+D800到U+DBFF(空间大小2的10次方),称为高位(H),后10位映射在U+DC00到U+DFFF(空间大小2的10次方),称为低位(L),这意味着一个辅助平面的字符被拆成两个基本平面的字符表示。所有当我们遇到两个字节,发现它的码点位于U+D8000到U+DBFF之间,就可以断定紧跟在这两个字节后面的码点必定在U+DC000到U+DFFF之间,同时这四个字符应该同时解读。
Unicode码点转换为UTF-16的时候,首先要区分是基本平面字符还是辅助平面字符。如果是前者,直接将码点转化为对应的16进制形式,长度为两字节:U+597D = 0x597D。如果是辅助平面字符,转码方式如下:
H = Math.floor((c-0x10000) / 0x400)+0xD800L = (c - 0x10000) % 0x400 + 0xDC00
以字符为例,它是一个辅助平面字符,码点为U+1D306,将其转为UTF-16的计算过程如下。
H = Math.floor((0x1D306-0x10000)/0x400)+0xD800 = 0xD834L = (0x1D306-0x10000) % 0x400+0xDC00 = 0xDF06
即次字符的UTF-16编码为:0xD834 DF06,长度为四个字节。

3、Java中的UTF-16

java中使用char(两个字节)作为表示字符的单位,因此在java中单个char是无法表示辅助面字符的,于是在java中BMP字符用一个char表示,而辅助面字符使用两个char表示。java中使用代码点(Unicode code pointer)表示范围在U+0000到U+10FFFF之间的字符值,代码单元(Unicode code unit)表示用于作为UTF-16编码的16位(两个字节)char值。也就是说,某个字符的代码点数量为1,而代码单元数量为2.所以代码单元的数量并不是代表字符的数量。
String中,索引值指的是代码单元,所以辅助面字符在String中占两个位置。也就是说,int length()方法返回的是代码单元的数量,如果字符串中含有辅助面字符,该方法返回的值并不是实际的字符数。
通过下面实例可能更好的帮助理解:
public class test{    public static void main(String[] args)    {        char[] chs = Character.toChars(0x10400);        System.out.printf("U+10400 高代理字符:%04x%n",(int)chs[0]);        System.out.printf("U+10400 低代理字符:%04x%n",(int)chs[1]);        String str = new String(chs);        System.out.println(str.charAt(0));        System.out.println(str.charAt(1));        System.out.println("代码单元长度"+str.length());        System.out.println("代码点数量"+str.codePointCount(0,str.length()));        System.out.println(str.codePointAt(0));    }}
输出结果如下所示:
U+10400 高代理字符:d801U+10400 低代理字符:dc00??代码单元长度2代码点数量166560
0 0
原创粉丝点击