Java 字符是 Unicode
Java 语言设计之初就确立了“一切皆 Unicode”的原则,这意味着:

char类型本质是 Unicode:Java 的char类型是一个无符号的 16 位整数,它专门用来表示一个 Unicode 字符。- 固定大小:由于
char是 16 位,它可以直接表示 Unicode 字符集中的基本多文种平面 中的字符,BMP 包含了世界上绝大多数语言的常用字符,大约有 65536 个(从U+0000到U+FFFF)。 - 超出 BMP 的字符:对于 BMP 之外的字符(如某些生僻汉字、emoji 表情符号等),它们需要用代理对 来表示,这是一个 32 位的字符,由两个 16 位的
char组成。
char 类型和 Unicode 字面量
在 Java 中,你可以通过以下几种方式来表示一个 char 类型的 Unicode 字符:
a) 直接使用字符
char a = 'A'; char zh = '中';
b) 使用 Unicode 转义序列
这是最直接的方式,格式为 \u 后面跟 4 个十六进制数字。
// \u0041 是 'A' 的 Unicode 码点 char a = '\u0041'; // \u4e2d 是 '中' 的 Unicode 码点 char zh = '\u4e2d'; System.out.println(a); // 输出 A System.out.println(zh); // 输出 中
c) 使用十进制码点
虽然 char 本质是 16 位整数,但你不能直接给它赋一个超出 char 范围(0-65535)的十进制值,对于基本字符,可以这样:
// 'A' 的十进制码点是 65 char a = 65; System.out.println(a); // 输出 A
处理超出 BMP 的字符(代理对)
Unicode 字符数量已经远远超过了 65536 个,这些字符位于辅助平面,它们的码点范围是 U+10000 到 U+10FFFF。

Java 的 char 无法直接存储这么大的码点,Java 使用了 UTF-16 编码方案,将这些 32 位的字符表示为两个 16 位的 char,这就是代理对。
- 高位代理:范围是
\uD800到\uDBFF。 - 低位代理:范围是
\uDC00到\uDFFF。
一个完整的代理对由一个高位代理和一个低位代理组成。
示例:Emoji 表情符号 😊
的 Unicode 码点是 U+1F60A。
-
计算代理对:
(图片来源网络,侵删)- 码点
0x1F60A减去0x10000得到0xF60A。 - 将
0xF60A转换为 20 位的二进制:0000 1111 0110 0000 1010。 - 前 10 位是高位代理值:
0000111101(十进制 637, 十六进制0x276D),加上高位代理的偏移量0xD800,得到高位代理字符:\uD83D。 - 后 10 位是低位代理值:
100001010(十进制 274, 十六进制0x0D0A),加上低位代理的偏移量0xDC00,得到低位代理字符:\uDE0A。 - 在 Java 中由
char'\uD83D'和char'\uDE0A'组成。
- 码点
-
在代码中处理:
直接写 char c = '😊'; 是可以的,Java 编译器会自动将其解析为代理对。
// 直接使用 emoji 字面量,编译器会处理成代理对
char emoji = '😊';
// 验证它由两个 char 组成
System.out.println("字符数量: " + Character.charCount(emoji)); // 输出 2
System.out.println("第一个 char (高位代理): " + (int)Character.highSurrogate(emoji)); // 输出 55357 (0xD83D)
System.out.println("第二个 char (低位代理): " + (int)Character.lowSurrogate(emoji)); // 输出 56842 (0xDE0A)
// 错误示范:不能单独操作代理对中的一个 char
// char wrong = '\uD83D'; // 这是一个无效的孤立代理
Character 类:字符操作的核心工具类
java.lang.Character 类提供了大量静态方法来操作 char 类型,特别是处理代理对和 Unicode 属性。
重要方法:
-
charCount(int codePoint)判断给定的 Unicode 码点是一个char还是一个代理对。- 返回 1:表示码点在 BMP 内。
- 返回 2:表示码点在辅助平面内,需要代理对。
System.out.println("'A' 的 charCount: " + Character.charCount('A')); // 1 System.out.println("'😊' 的 charCount: " + Character.charCount(0x1F60A)); // 2 -
toChars(int codePoint)将一个 Unicode 码点转换为一个char数组,如果码点在 BMP 内,数组长度为 1;如果在辅助平面内,数组长度为 2。char[] charsA = Character.toChars('A'); // charsA = ['A'] char[] charsEmoji = Character.toChars(0x1F60A); // charsEmoji = ['\uD83D', '\uDE0A'] -
codePointAt(char[] a, int index)从一个char数组中读取一个 Unicode 码点,它会自动处理代理对。char[] emojiArray = {'\uD83D', '\uDE0A'}; int codePoint = Character.codePointAt(emojiArray, 0); System.out.println("从数组中解析出的码点: " + codePoint); // 输出 128522 (0x1F60A) System.out.println("对应的字符: " + new String(emojiArray, 0, Character.charCount(codePoint))); // 输出 😊 -
isDigit(char c),isLetter(char c)等 判断字符的 Unicode 属性。System.out.println("'a' 是字母吗? " + Character.isLetter('a')); // true System.out.println("'1' 是数字吗? " + Character.isDigit('1')); // true
String 与 Unicode 的关系
String 在 Java 中是不可变的字符序列,它内部使用 char 数组来存储字符。
- 对于 BMP 字符:
String中的一个char对应一个字符。 - 对于辅助平面字符:
String中的两个char组成一个字符。
String 的 length() 方法返回的是 char 数组的长度,而不是 Unicode 字符的数量。
String s1 = "Hello";
String s2 = "A😊B";
System.out.println("s1 的长度: " + s1.length()); // 5
System.out.println("s2 的长度: " + s2.length()); // 4 (A, 😊(2个char), B)
// 要获取真正的 Unicode 字符数量
int codePointCount = s2.codePointCount(0, s2.length());
System.out.println("s2 的 Unicode 字符数量: " + codePointCount); // 3
I/O 与编码
虽然 Java 内部使用 UTF-16,但在进行文件读写或网络传输时,数据需要被序列化为字节流,这时就需要指定字符编码。
InputStreamReader/OutputStreamWriter:用于在字节流和字符流之间进行转换,需要指定编码(如 "UTF-8", "GBK")。String.getBytes(String charsetName):将String编码为指定格式的字节数组。new String(byte[] bytes, String charsetName):用指定编码的字节数组创建String。
最佳实践:除非有特殊兼容性要求,否则始终使用 UTF-8 作为编码标准,因为它能表示所有 Unicode 字符,且是互联网事实上的标准。
String text = "你好,世界!😊";
// 使用 UTF-8 编码为字节数组
byte[] utf8Bytes = text.getBytes(StandardCharsets.UTF_8);
// 使用 GBK 编码(可能丢失 emoji)
byte[] gbkBytes = text.getBytes("GBK"); // 注意:GBK 不支持 emoji
// 从字节数组解码回 String
String fromUtf8 = new String(utf8Bytes, StandardCharsets.UTF_8);
String fromGbk = new String(gbkBytes, "GBK");
System.out.println("UTF-8 解码后: " + fromUtf8); // 你好,世界!😊
System.out.println("GBK 解码后: " + fromGbk); // 你好,世界?� (emoji 丢失或变成乱码)
| 概念 | 描述 | 关键点 |
|---|---|---|
char 类型 |
Java 的基本字符类型,本质是 16 位无符号整数。 | 用于存储单个 Unicode 字符。 |
| Unicode 码点 | 一个字符在 Unicode 字符集中的唯一编号。 | 如 'A' 是 U+0041,'中' 是 U+4E2D。 |
| BMP (基本多文种平面) | Unicode 字符集的前 65536 个字符 (U+0000 - U+FFFF)。 |
可以用一个 char 直接表示。 |
| 辅助平面 | Unicode 字符集超出 BMP 的部分 (U+10000 - U+10FFFF)。 |
必须用代理对(两个 char)表示。 |
| 代理对 | 由高位代理 (\uD800 - \uDBFF) 和低位代理 (\uDC00 - \uDFFF) 组成,用于表示辅助平面的字符。 |
如 由 '\uD83D' 和 '\uDE0A' 组成。 |
String.length() |
返回 char 数组的长度,不是 Unicode 字符的数量。 |
对于包含 emoji 的字符串,length() 会比实际字符数多。 |
String.codePointCount() |
返回 String 中真正的 Unicode 字符数量。 |
正确处理了代理对。 |
| 字符编码 | 将字符(Unicode)转换为字节流的过程。 | Java 内部是 UTF-16,但 I/O 时应优先使用 UTF-8。 |
理解这些概念对于正确处理国际化文本、特殊符号和不同语言的编程至关重要。
