
Java char 类型
Java 的 char 类型采用 UTF-16 编码。
提到 UTF-16 ,那就绕不开 Unicode 字符集。
关于 Unicode 的相关知识在本文中不再进行赘述,毕竟本文的重点在于《Java 中的 char 类型》,还不了解的可以看看 字符编码笔记:ASCII,Unicode 和 UTF-8
看了很多相关博客,谈到这个话题无一例外一般都会涉及到下面两个名词:
- 码点(Unicode code point)
- 代码单元(code unit)
码点
那么什么是码点呢?
码点是指一个编码表中的某个字符所对应的代码值。
顾名思义Unicode code point
(即本文中的码点)指的就是 Unicode 编码集中的某个字符所对应的代码值。
由这个定义不难发现,一个字符串中有多少个字符就有多少个码点。
即 CodePointCount = CharacterCount
Unicode的码点分为17个代码级别,第一个级别是基本的多语言级别,码点从U+0000——U+FFFF,其余的16个级别从U+10000——U+10FFFF,其中包括一些辅助字符。
基本的多语言级别,每个字符用16位表示,而辅助字符采用连续的代码单元进行编码。
代码单元
接下来是代码单元。
代码单元的定义其实更简单。
代码单元即在具体编码形式中的最小单位,比如,使用 UTF-16 编码时一个代码单元为16位,使用 UTF-8 编码时一个代码单元为8位。
Java 中的 String 类的 length 方法 是以 代码单元 为单位计数,故一个字符串的 length >= codePointCount
Code
/**
* This code is used to test the code point of the character.
* @version 1.0 2022-06-16
* @author Rekord
*/
// Code Point
// Code Unit
public class CodePoint {
public static void main(String[] args) {
String hello = "h𝕆i";
System.out.println(hello.length());//4
System.out.println(hello.codePointCount(0, hello.length()));//3
System.out.println(hello.codePointBefore(4)); // 识别前一个码点
for (int i = 0; i < hello.length(); i++) {
char c = hello.charAt(i);
System.out.println(c + ": " + Integer.toHexString(c));
}
System.out.println('\u997e' + " " + '\u6662');
int codePointCount = hello.codePointCount(0, hello.length());
for(int i = 0; i < codePointCount; i++) {
int index = hello.offsetByCodePoints(0, i);
int charAt = hello.codePointAt(index);
System.out.println("index: " + index + "; HexValue: " + Integer.toHexString(charAt));
}
boolean isValid = Character.isValidCodePoint(0x997e);
System.out.println(isValid);
String str_en = "Hello, World!";
String str_cn = "你好,世界!";
System.out.println(str_cn.length()); // 6
System.out.println(str_cn.codePointCount(0, str_cn.length())); // 6
// char ch = '𝕆'; // 编译不通过
char[] chars = Character.toChars(0x1d546);
String str = new String(chars);
System.out.println(str); // 𝕆
}
}
分析
上述代码的13行和14行:
System.out.println(hello.length());//4
System.out.println(hello.codePointCount(0, hello.length()));//3
输出为什么是4和3呢?
由于 hello = "h𝕆i"
包含3个字符,故码点数(codePointCount)为3。
由于 𝕆
字符不属于基本的多语言级别,从编码0x1d546
就可见一斑(>2B),在底层该字符需要两个 utf-16 才能正确表示(即需要两个代码单元)。
而 length 的计数方式又是依据代码单元,故 length = 1 + 2 + 1 。
另外因为 Java 中的 char 都是 UTF-16 ,即占两个字节(一个代码单元)。所以
char ch = '𝕆'; // '𝕆' 需要两个代码单元
是错误的。
后面的17~28行也验证了上述理论的合理性。
正因为上述种种,故40行的Charater.toChars
方法返回的是一个 char 数组,而非单个 char 。
因为某些字符是无法使用一个代码单元(char)就能正确表示的。
“不恰当”的插曲
在学习这些知识时,由于涉及到的内容相当基础,所以仅仅使用 vscode 作为编辑器,并且没有使用任何 Java 插件,均为手动编译运行。
然而在印证这些理论知识时出现了很多奇怪的问题。
最终问题定位于手动编译时没有指定自定义参数。
因为在和编码打交道,然而 javac 编译时默认编码又不为 utf-8 。(话说这年头不会有人源文件还不使用 UTF-8 编码吧?)
所以严重的时候(不严重的时候更加难以定位错误)就会报类似于 “未结束的字符文字” 这种错误。
解决方案倒是很简单:
javac -encoding UTF-8 xxx.java
java xxx
所以在这里还是建议大家在研究、测试和验证这个主题时尽量使用 IDE 。