一个Java字符串中到底有多少个字符?

依照Java的文档, Java中的字符内部是以UTF-16编码方式表示的,最小值是 \u0000 (0),最大值是\uffff(65535), 也就是一个字符以2个字节来表示,难道Java最多只能表示 65535个字符?

char: The char data type is a single 16-bit Unicode character. It has a minimum value of '\u0000' (or 0) and a maximum value of '\uffff' (or 65,535 inclusive).html

from The Java™ Tutorialsjava

首先,让咱们先看个例子:api

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Main {
 
public static void main(String[] args) {
// 中文常见字
String s = "你好";
System.out.println( "1. string length =" + s.length());
System.out.println( "1. string bytes length =" + s.getBytes().length);
System.out.println( "1. string char length =" + s.toCharArray().length);
System.out.println();
 
// emojis
s = "👦👩";
System.out.println( "2. string length =" + s.length());
System.out.println( "2. string bytes length =" + s.getBytes().length);
System.out.println( "2. string char length =" + s.toCharArray().length);
System.out.println();
 
// 中文生僻字
s = "𡃁妹";
System.out.println( "3. string length =" + s.length());
System.out.println( "3. string bytes length =" + s.getBytes().length);
System.out.println( "3. string char length =" + s.toCharArray().length);
System.out.println();
}
}

运行这个程序,你以为输出结果是什么?数组

输出结果:oracle

1
2
3
4
5
6
7
8
9
10
11
1. string length =2
1. string bytes length =6
1. string char length =2
 
2. string length =4
2. string bytes length =8
2. string char length =4
 
3. string length =3
3. string bytes length =7
3. string char length =3

咱们知道, String.getBytes()若是不指定编码格式,Java会使用操做系统的编码格式获得字节数组,在个人MacOS中,默认使用UTF-8做为字符编码(locale命令能够查看操做系统的编码),因此在个人机器运行,String.getBytes()会返回UTF-8编码的字节数组。app

String.length返回Unicode code units的长度。ui

String.toCharArray返回字符数组。编码

咱们设置的字符串都是两个unicode字符,输出结果:spa

  • 普通的中文字:字符串的长度是2,每一个中文字按UTF-8编码是三个字节,字符数组的长度看起来也没问题
  • emojis字符: 咱们设置了两个emojis字符,男女头像。结果字符串的长度是4UTF-8编码8个字节,字符数组的长度是4
  • 生僻的中文字:咱们设置了两个中文字,其中一个是生僻的中文字。结果字符串的长度是3, UTF-8编码7个字节,字符数组的长度是3

看起来字符串的字符数和咱们预期的有点不同,咱们的字符串只有两个unicode字符, 但是输出结果有时候是2,有时候是3, 有时候是4,为何呢?
这还得从Java的历史提及。操作系统

Java最初设计的Charactor用两个字节来表示unicode字符,这没有问题, 由于最初unicode中的字符还比较少, Java 1.1以前采用Unicode version 1.1.5, JDK 1.1中支持Unicode 2.0, JDK 1.1.7支持Unicode 2.1, Java SE 1.4 支持 Unicode 3.0, Java SE 5.0开始支持Unicode 4.0

直到Unicode 3.0, Java用两个字节来表示unicode字符尚未问题,由于Unicode 3.0最多49,259个字符, 两个字节能够表示65,535个字符,还足够容的下全部的uicode3.0字符。

可是Unicode 4.0(事实上自Unicode 3.1), 字符集进行很大的扩充,已经达到了96,447个字符,Unicode 11.0已经包含137,374个字符。

在Unicode中,为每个字符对应一个编码点(一个整数),用 U+紧跟着十六进制数表示。全部字符按照使用上的频繁度划分为 17 个平面(编号为 0-16),即基本的多语言平面和增补平面。基本的多语言平面(英文为 Basic Multilingual Plane,简称 BMP)又称平面 0,收集了使用最普遍的字符。

这样一来,Java的Charactor的两个字节的设计,已经不足以容纳全部的Unicode 4的字符, 因此可能须要4个字节才能表示扩展字符,因此如今的Charactor表明的已经再也不是一个字符 (代码点 code point), 而是一个代码单元(code unit)。

  • Code Point: 代码点,一个字符的数字表示。一个字符集通常能够用一张或多张由多个行和多个列所构成的二维表来表示。二维表中行与列交叉的点称之为代码点,每一个码点分配一个惟一的编号数字,称之为码点值或码点编号,除开某些特殊区域(好比代理区、专用区)的非字符代码点和保留代码点,每一个代码点惟一对应于一个字符。 从U+0000 到 U+10FFFF

  • Code Unit:代码单元,是指一个已编码的文本中具备最短的比特组合的单元。对于 UTF-8 来讲,代码单元是 8 比特长;对于 UTF-16 来讲,代码单元是 16 比特长。换一种说法就是 UTF-8 的是以一个字节为最小单位的,UTF-16 是以两个字节为最小单位的。

Java的字符在内部以UTF-16编码方式来表示,String.length返回的是Code Unit的长度,而再也不是Unicode中字符的长度。对于传统的BMP平面的代码点,String.length和咱们传统理解的字符的数量是一致的,对于扩展的字符,String.length多是咱们理解的字符长度的两倍。

有可能你会问, 对于一个UTF-16编码的扩展字符,它以4个字节来表示,那么前两个字节会不会和BMP平面冲突,致使程序不知道它是扩展字符仍是BMP平面的字符?

实际上是不会的, 幸运的是, 在BMP平面中, U+D800U+DFFF之间的码位是永久保留不映射到Unicode字符,UTF-16就利用保留下来的0xD800-0xDFFF区块的码位来对辅助平面的字符的码位进行编码。

UTF-16编码中,辅助平面中的码位从U+10000U+10FFFF,共计FFFFF个,须要20位来表示。第一个整数(两个字节,称为前导代理)要容纳上述20位的前10位,第二个整数(称为后尾代理)容纳上述20位的后10位。前导代理的值的范围是0xD8000xDBFF,后尾代理的0xDC00~0xDFFF。能够看到前导代理和后尾代理的范围都落在了BMP平面中不用来映射的码位,因此不会产生冲突,并且前导代理和后尾代理也没有重合。这样咱们获得两个字节的,就能够直接判断它是不是BMP平面的字符,仍是扩展字符中的前导代理仍是后尾代码。

国外的有些用户用emojis字符作本身的昵称,致使有些系统不能正确的显示出来,这是由于这些系统粗暴的使用Charactor来表示,在显示的时候截断的时候有时候可能不是在正确的代码点上进行截断。

咱们在进行字符串截取的时候,好比String.substring有可能会踩到一些坑,尤为常用的emojis字符。

自 Java 1.5 java.lang.String就提供了Code Point方法, 用来获取完整的Unicode字符和Unicode字符数量:

  • public int codePointAt(int index)
  • public int codePointBefore(int index)
  • public int codePointCount(int beginIndex, int endIndex)

注意这些方法中的index使用的是code unit值。

参考文档

    1. https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html
    2. https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/lang/Character.html
    3. http://www.oracle.com/us/technologies/java/supplementary-142654.html
    4. https://stackoverflow.com/questions/2533097/java-unicode-encoding
    5. https://docs.oracle.com/javase/specs/jls/se7/html/jls-3.html
    6. https://zh.wikipedia.org/wiki/Unicode
    7. https://codeahoy.com/2016/05/08/the-char-type-in-java-is-broken/
    8. https://zh.wikipedia.org/wiki/UTF-16
    9. https://wiki.sei.cmu.edu/confluence/display/java/STR50-J.+Use+the+appropriate+method+for+counting+characters+in+a+string
    10. http://stn.audible.com/abcs-of-unicode/#common-unicode-mistakes-in-java-apps
相关文章
相关标签/搜索