当前位置: 首页 > 科技观察

一个字符串可以有多少个字符?我搞错了

时间:2023-03-15 13:20:08 科技观察

根据Java文档,Java中的字符是用UTF-16编码表示的,最小值为\\u0000(0),最大值为\\uffff(65535),即一个字符由2个字节表示。Java最多只能表示65535个字符吗?char:char数据类型是单个16位Unicode字符。它的最小值为'\\u0000'(或0),最大值为'\\uffff'(或65,535)。来自TheJava?Tutorials首先,让我们看一个例子:运行这个程序,你认为输出是什么?输出:我们知道,String.getBytes()如果不指定编码格式,Java会使用操作系统的编码格式来获取字节数组。在我的MacOS中,默认使用UTF-8作为字符编码(locale命令可以查看操作系统的编码),所以在我的机器运行时,String.getBytes()会返回一个UTF-8编码的字节数组。String.length返回Unicode代码单元的长度。String.toCharArray返回一个字符数组。我们设置的字符串都是两个unicode字符,输出结果为:普通汉字:字符串长度为2,每个汉字按照UTF-8编码为三个字节,字符数组长度不似乎是问题表情符号字符:我们设置了两个表情符号字符,男性和女性头像。结果字符串长度为4,UTF-8编码为8字节,字符数组长度为4个生僻汉字:我们设置了两个汉字,其中一个是生僻汉字。得到的字符串长度为3,UTF-8编码为7字节,字符数组长度为3。看来字符串的字符数和我们预期的有点不一样。我们的字符串只有两个unicode字符,但是输出结果有时是2,有时是3,有时是4,为什么呢?这还得从Java的历史说起。Java最初设计的Charactor使用两个字节来表示unicode字符。这个没问题,因为一开始unicode的字符比较少。Java1.1之前使用Unicode1.1.5版本,JDK1.1开始支持Unicode2.0,JDK1.1.7支持Unicode2.1,JavaSE1.4支持Unicode3.0,JavaSE5.0开始支持Unicode4.0。直到Unicode3.0,Java用两个字节表示unicode字符没有问题,因为Unicode3.0最多可以有49259个字符,两个字节可以表示65535个字符,足以容纳所有的uicode3.0字符。但是到了Unicode4.0(实际上是从Unicode3.1开始),字符集已经大大扩展到96,447个字符,而Unicode11.0已经包含了137,374个字符。在Unicode中,每个字符对应一个代码点(一个整数),由U+后跟一个十六进制数表示。所有字符按使用频率分为17个平面(编号0-16),即基本多语言平面和补充平面。BasicMultilingualPlane(英文为BasicMultilingualPlane,简称BMP),又称0平面,收集了目前使用最广泛的字符。这样Java的Charactor的两字节设计不足以容纳所有的Unicode4个字符,所以可能需要4个字节来表示扩展字符,所以现在的Charactor表示的不再是一个字符(codepoint代码点),而是一个代码单元(codeunit)。CodePoint:代码点,一个字符的数字表示。一个字符集一般可以用一个或多个由多行多列组成的二维表来表示。二维表中行和列相交的点称为码点,每个码点都分配一个唯一的编号,称为码点值或码点编号,某些特殊区域除外(如机关区域、dedicatedarea)的非字符码点和保留码点,每一个都唯一对应一个字符。从U+0000到U+10FFFF。代码单元:代码单元是指编码文本中具有最短位组合的单元。对于UTF-8,一个编码单元是8位长;对于UTF-16,一个代码单元是16位长。也就是说,UTF-8以一个字节为最小单位,UTF-16以两个字节为最小单位。Java字符在内部是用UTF-16编码表示的,String.length返回的是CodeUnit的长度,不是Unicode中字符的长度。对于传统BMP平面的码位,String.length与我们传统理解的字符数相同。对于扩展字符,String.length可能是我们理解的字符长度的两倍。你可能会问,对于一个UTF-16编码的扩展字符,用4个字节表示,前两个字节会不会和BMP平面冲突,导致程序不知道是扩展字符还是BMP平面?其实,这不可能。幸运的是,在BMP平面中,U+D800和U+DFFF之间的代码点是永久保留的,没有映射到Unicode字符。UTF-16使用保留的0xD800-0xDFFF块的码位来编码辅助平面字符的码位。在UTF-16编码中,辅助平面中的码位从U+10000到U+10FFFF,一共FFFFF,需要20位来表示。第一个整数(两个字节,称为首位代理)用于容纳上述20位中的前10位,第二个整数(称为尾部代理)用于容纳上述20位中的后10位。前导代理取值范围为0xD800~0xDBFF,尾代理取值范围为0xDC00~0xDFFF。可以看出,leadingagent和trailingagent的范围都在BMP平面中不用于映射的codepoints之内,所以不会有冲突,leadingagent和trailingagent不会重叠。这样,如果我们得到两个字节,我们可以直接判断它是BMP平面的字符,还是扩展字符中的前导或尾随码。部分国外用户使用emojis字符作为昵称,导致部分系统显示不正确。这是因为这些系统使用Charactor粗略地表示它们,有时在显示时被截断时可能不在正确的代码点。截断。我们在进行字符串截取的时候,比如String.substring,可能会踩到一些陷阱,尤其是经常使用的emojis字符。自Java1.5java.lang.String提供了获取完整Unicode字符和Unicode字符个数的CodePoint方法:publicintcodePointAt(intindex)publicintcodePointBefore(intindex)publicintcodePointCount(intbeginIndex,intendIndex)注意这些方法中的index使用的是codeunit价值。