什么是Unicode
Unicode是一种字符编码方案,它为每种语言中的每个字符都设定了统一唯一的二进制编码,也就是规定了二进制和字符的映射,以实现跨语言、跨平台进行文本转换、处理的要求,但是它并没有规定这些二进制该如何存储和传输.
同步立场
总所周知,Java中的char数据类型采用的是UTF-16编码表示Unicode码点的代码单元,而Java字符串由char值序列组成(jdk8之前是这样的,jdk9之后就变为byte数组了).最常用的Unicode字符使用一个代码单元就可以表示,也就是两个字节,两个字节最多只能表示到6w多个字符,那么剩下的表示就需要用到辅助字符,辅助字符需要一对代码单元表示,也就是四个字节来表示.
也就是说在UTF-16中并不是说只能有两个字节,而是以两个字节为最小单元,也就是代码单元(code unit),一个代码单元包含两个字节.对于基本字符,UTF-16使用一个代码单元就能表示完全,但是对于剩下的字符,我们称之为辅助字符,辅助字符使用一对代码单元组成.
为了简化表达,在使用Unicode字符的时候通常会使用如下表达方法:
基本字符:U+0000~U+FFFF
辅助字符:U+010000~U+10FFFF
这样,就将需要使用到的字符分配成了17个平面.
这些用来表示字符的编号,我们称之为码点(code point),一个编号对应了一个具体的码点
一个码点我们表示为一个符号,这个符号可以是中文,英文,等各种文字,当然也包括 🍺 等emoji表情.常用的英文这些自然是只需要一个代码单元,但是对于 🍺 ,就需要两个代码单元.
而我们最常用的length方法返回的是采用UTF-16编码表示给定字符串所需要的代码单元数量.
下图也可以表现出length返回的是代码单元数量而非字符数量
用幼儿园知识我们都能知道平均一个emoji表情占两个代码单元.而部分中文占1个代码单元,因此这个时候,对于一个emoji表情,他的一个码点拥有两个代码单元,对于一个中文,它只有一个代码单元.
因此如果想要获得的是字符的数量,也就是码点的数量,那么应该使用codePointCount方法
你还可以使用codePoints().toArray()方法来获取字符串中的所有码点
同时,由于char类型只能装载2个字节,因此如果你给他一个辅助字符,它就会报错,因此对于这种辅助字符,我们需要使用String来装载.
API中的方法与上面知识的关系
char charAt(int index)
charAt方法我相信你一定使用过,但是如果你使用charAt方法获取到的是辅助字符中的某一个代码单元,那么返回的是乱码.
int offsetByCodePoint(int start, int cpCount)
这个方法返回的是从start这个码点开始,cpCount个码点后的码点索引,当然,他返回的是相对于代码单元数量的索引.如果你不明白,看下面的代码你就明白了
可以看到跳过1个码点返回的int值为2,也就是下一个码点的起始位置是代码单元2(代码单元从0开始,和索引差不多),因此跳过三个代码单元后,得到的是6,聪明的你一定能理解为什么说这个方法返回的是代码单元的数量了.
通过这个代码单元数量,我们可以知道一个字符串中有多少个字符
写法如下
IntStream codePoints()
当前方法用于返回字符串对应的码点流,通常配合toArray()方法使用来得到当前字符串对应的码点对应的UTF-16的int值.
同理,既然知道了码点的个数,我们自然也能用来判断这段文字中的字数,也就能对文字长度进行限制了.方法如下:
public String(int[] codePoints, int offset, int count)
这是String的一个构造方法,其中很明显的就展示出来了其中的一个参数需要使用到codePoints数组也就是一个码点数组,offset表示开始索引,count表示使用多少个码点.
配合上面的得到字符串长度的方法,我们就可以将所需要的包含辅助字符的数组将其变换为对应字符串了.
boolean isSupplementaryCodePoint(int codePoint)
boolean isSurrogate(char ch)
这两个方法用于判定字符是否是辅助字符