关于Java的代码点(codePoint)、unicode编码、UTF-8、UTF-16

something before start

看Character和String的时候发现的小知识点,挺好玩的
Java采用的是UTF-16,基本字符(BMP)采用一个bit存储,增补字符采用俩

unicode

ASCII 码一共定义了 128 个字符,英语用 128 个字符来编码完全是足够的,但是用来表示其他语言,128 个字符是远远不够的。

Unicode是展示世界上所有语言中的所有字符的标准方案,他给所有的字符指定了一个数字用来表示该字符。但是Unicode并没有规定字符对应的二进制怎么存储。

Unicode字符平面映射

目前的Unicode字元分为17组编排,每组称为平面(Plane),而每平面拥有65536(即216)个代码点。然而目前只用了少数平面。

平面 始末字元值 中文名称 英文名称
0号平面 U+0000 - U+FFFF 基本多文种平面 Basic Multilingual Plane,简称BMP
1号平面 U+10000 - U+1FFFF 多文种补充平面 Supplementary Multilingual Plane,简称SMP
2号平面 U+20000 - U+2FFFF 表意文字补充平面 Supplementary Ideographic Plane,简称SIP
3号平面 U+30000 - U+3FFFF 表意文字第三平面(未正式使用) Tertiary Ideographic Plane,简称TIP
4号平面 至 13号平面 U+40000 - U+DFFFF (尚未使用)
14号平面 U+E0000 - U+EFFFF 特别用途补充平面 Supplementary Special-purpose Plane,简称SSP
15号平面 U+F0000 - U+FFFFF 保留作为私人使用区(A区) Private Use Area-A,简称PUA-A
16号平面 U+100000 - U+10FFFF 保留作为私人使用区(B区) Private Use Area-B,简称PUA-B

增补字符是代码点在 U+10000 至 U+10FFFF 范围之间的字符(上述表格中1号平面~16号平面之间的),也就是那些使用原始的 Unicode 的 16 位设计无法表示的字符。从 U+0000 至 U+FFFF 之间的字符集有时候被称为基本多语言面 (BMP)。因此,每一个 Unicode 字符要么属于 BMP,要么属于增补字符。

因为字符很多嘛,所以就会有的字符需要两个字节,甚至三四个字节才能表示,如果直接全用四个字节表示那就太浪费空间。于是,为了较好的解决 Unicode 的编码问题, UTF-8、UTF-16等编码模式 出现了。

UTF-8

UTF-8 是可变长编码。它可以使用 1 - 4 个字节表示一个字符,根据字符的不同变换长度。

编码规则如下:

对于单个字节的字符,第一位设为 0,后面的 7 位对应这个字符的 Unicode 码点。因此,对于英文中的 0 - 127 号字符,与 ASCII 码完全相同。

对于需要使用 N 个字节来表示的字符(N > 1),第一个字节的前 N 位都设为 1,第 N + 1 位设为0,剩余的 N - 1 个字节的前两位都设位 10,剩下的二进制位则使用这个字符的 Unicode 码点来填充。

Unicode 十六进制码点范围 UTF-8 二进制
0000 0000 - 0000 007F 0xxxxxxx
0000 0080 - 0000 07FF 110xxxxx 10xxxxxx
0000 0800 - 0000 FFFF 1110xxxx 10xxxxxx 10xxxxxx
0001 0000 - 0010 FFFF 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx

UCS-2

UCS-2对每一个Unicode码位使用2bytes字集(16位bit);
UCS-4对每一个Unicode码位使用4bytes字集(32位bit);
UTF-16可看成是UCS-2的父集。
在没有辅助平面字符(surrogate code points)前,UTF-16与UCS-2所指的是同一的意思。但当引入辅助平面字符后,就称为UTF-16了。

UTF-16

代理区

从U+D800到U+DFFF的码位(代理区)

因为Unicode字符集的编码值范围为0-0x10FFFF,而大于等于0x10000的辅助平面区的编码值无法用2个字节来表示,所以Unicode标准规定:基本多语言平面内,U+D800..U+DFFF的值不对应于任何字符,为代理区。因此,UTF-16利用保留下来的0xD800-0xDFFF区段的码位来对辅助平面的字符的码位进行编码。

但是在使用UCS-2的时代,U+D800..U+DFFF内的值被占用,用于某些字符的映射。但只要不构成代理对,许多UTF-16编码解码还是能把这些不符合Unicode标准的字符映射正确的辨识、转换成合规的码元. 按照Unicode标准,这种码元串行本来应算作编码错误.

BMP

从U+0000至U+D7FF以及从U+E000至U+FFFF的码位

第一个Unicode平面(BMP),码位从U+0000至U+FFFF(除去代理区),包含了最常用的字符。UTF-16与UCS-2编码在这个范围内的码位为单个16比特长的码元,数值等价于对应的码位。BMP中的这些码位是仅有的码位可以在UCS-2被表示。

辅助平面

从U+10000到U+10FFFF的码位,在UTF-16中被编码为一对16比特长的码元(即32bit,4Bytes),称作 code units called a 代理对(surrogate pair),具体方法是:

Ø 码位减去0x10000, 得到的值的范围为20比特长的0..0xFFFFF(因为Unicode的最大码位是0x10ffff,减去0x10000后,得到的最大值是0xfffff,所以肯定可以用20个二进制位表示),写成二进制形式:yyyy yyyy yyxx xxxx xxxx。

Ø 高位的10比特的值(值的范围为0..0x3FF)被加上0xD800得到第一个码元或称作高位代理(high surrogate), 值的范围是0xD800..0xDBFF。由于高位代理比低位代理的值要小,所以为了避免混淆使用,Unicode标准现在称高位代理为前导代理(lead surrogates)。

Ø 低位的10比特的值(值的范围也是0..0x3FF)被加上0xDC00得到第二个码元或称作低位代理(low surrogate), 现在值的范围是0xDC00..0xDFFF。 由于低位代理比高位代理的值要大,所以为了避免混淆使用,Unicode标准现在称低位代理为后尾代理(trail surrogates)。

Ø 最终的UTF-16(4字节)的编码(二进制)就是:110110yyyyyyyyyy 110111xxxxxxxxxx。

codePoint

如上面提到的,在UTF-16中增补字符需要两个char来表示,比如😃 对应的表示是 \uD83D\uDE03 ,而如果只是截取一半,那这个字符是没有意义的。

在Java中,一个实际的完整的字符称作代码点(codePoint)

下面举个例子

String testCode = "ab\uD83D\uDE03";
int length = testCode.length();
int count = testCode.codePointCount(0, testCode.length()); //求出0到testCode.length()之间的代码点数目

System.out.println(testCode);
System.out.println("代码点数目:"+count);

//输出
ab😃
代码点数目:3

所以如果要对字符串进行严格的过滤的话,应该按照代码点来进行访问,而不是逐位访问char

举个栗子:

String testCode = "ab\uD83D\uDE03汉字";
int cpCount = testCode.codePointCount(0, testCode.length());
System.out.println("cpcount:"+cpCount);

for (int index = 0; index < cpCount; ++index) {
  //这里的i是字符的位置
  int i = testCode.offsetByCodePoints(0, index);
  //下个字符位置
  int nexti = testCode.offsetByCodePoints(0,index+1);
  int codepoint = testCode.codePointAt(i);
  String cur = testCode.substring(i,nexti);
                
  System.out.println("i:"+i+" index:"+index+ " cur:" + cur +"\tcodepoint:"+codepoint );
}
//输出     
cpcount:5
i:0 index:0 cur:a	codepoint:97
i:1 index:1 cur:b	codepoint:98
i:2 index:2 cur:😃	codepoint:128515
i:4 index:3 cur:汉	codepoint:27721
i:5 index:4 cur:字	codepoint:23383
posted @   sarise  阅读(1641)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示