java和c#中char本质是utf-16代码单元,取值0–65535(u+0000–u+ffff),仅覆盖bmp字符;超出bmp的字符需用高位代理(u+d800–u+dbff)与低位代理(u+dc00–u+dfff)组成的代理对表示,单个char不等于逻辑字符。

Java 和 C# 中的 char 类型,本质是 UTF-16 的一个代码单元(code unit),不是直接对应 Unicode 字符(code point),而是对应编码过程中的基本存储单位。它的取值范围由 UTF-16 编码规则和 16 位存储结构共同决定。
取值范围:0 到 65535(即 0x0000–0xFFFF)
char 占用固定 2 字节(16 位),无符号,因此合法数值只能是:
- 十进制:0 至 65535(216 − 1)
- 十六进制:0x0000 至 0xFFFF
- Unicode 表示:U+0000 至 U+FFFF
这个范围覆盖了 Unicode 的基本多语言平面(BMP)全部字符,比如英文字母、汉字常用字(如“中”U+4E2D)、标点、控制符等。
超出 BMP 的字符怎么办?靠代理对(surrogate pair)
Unicode 总共定义了 1,114,112 个码点(U+0000 到 U+10FFFF),但 BMP 只占前 65,536 个。其余在辅助平面(Supplementary Planes,U+10000–U+10FFFF)的字符,无法用单个 char 表示。
UTF-16 的解决方案是:用两个连续的 char 组成代理对,其中:
- 高位代理(high surrogate):范围 U+D800–U+DBFF(十进制 55296–56319)
- 低位代理(low surrogate):范围 U+DC00–U+DFFF(十进制 56320–57343)
- 二者组合后,可表示 U+10000–U+10FFFF 内任意码点,例如 ?(U+1F30D)需拆为
'\uD83C'+'\uDF0D'
注意:单独出现的 U+D800–U+DFFF 区间值不是合法字符,仅作为代理用途;若在字符串中遇到,必须成对解析。
常见误解澄清
很多人误以为 “char 就是一个字符”,但在现代 Unicode 场景下这不准确:
-
char c = '?'在 Java 中会编译失败——因为 ? 是 U+1F600,属于辅助平面,需用两个 char -
"?".length()返回 2(不是 1),因底层是char[]数组,含两个代理单元 -
Character.isSurrogate(c)或Character.isHighSurrogate(c)可用于安全检测 - 要获取真实字符数,应使用
String.codePointCount(0, s.length())
为什么设计成这样?历史与兼容性
Java 早期采用 UCS-2(固定 2 字节),假设所有字符都能放进 16 位。后来 Unicode 扩展,为保持向后兼容,没有改动 char 定义,而是引入 UTF-16 编码机制——BMP 仍单 char,扩展字符用代理对。C# 同样沿用该模型。
这种设计让旧代码在处理英文、常用汉字时完全不受影响,同时又能支持 emoji、古文字、数学符号等新字符,只是开发者需在涉及长度、索引、切片等操作时主动考虑代理对。











