在 Java 里处理文本时,字符串内部以 UTF-16 编码单元存储,每个 char 占 16 位。对于超出基本多文种平面(BMP)的字符,例如部分 emoji 或罕见汉字,会用两个 char 组成的代理对表示。如果只使用 charAt 方法,取到的是单个代理项,并非完整字符。String.codePointAt 方法则能从指定索引读出完整的 Unicode 码点,即使该位置存在双字节字符变量也能准确解析。

为什么 charAt 无法应对双字节字符变量
Java 的 String 底层是一个 char 数组,而 char 是 16 位无符号整数,最多表示 0 到 65535 的码元。Unicode 中 0x10000 以上的字符被称为补充字符,在 UTF-16 里必须拆成高代理项(0xD800-0xDBFF)和低代理项(0xDC00-0xDFFF)两个 char。当我们调用 charAt(index) 时,它只返回该索引处的 16 位值,若索引指向高代理项,得到的只是半个字符。
举例来说,字符「𠮷」(扩展 B 区)的码点是 0x20BB7,在字符串里占两个 char。若用 charAt 取第一个位置,得到的是高代理项 0xD842,而不是完整含义。这种拆分对循环遍历、截取子串、随机访问都埋下了隐患,尤其当数据源包含用户输入的表情或少数民族文字时,问题更易暴露。
codePointAt 的工作机制
String.codePointAt(int index) 会检查 index 处的 char 是否属于高代理项范围。如果是,并且 index+1 未越界且下一个 char 是低代理项,就把两者合成一个补充码点返回;否则直接将该 char 的值作为 BMP 码点返回。这意味着无论目标索引是普通字符还是双字节字符变量的起始位置,都能拿到正确的 int 型码点。
需要注意,index 参数指的是 char 单元的偏移,不是码点序号。若字符串前面有补充字符,那么同一个码点在 codePointAt 中的索引会比码点计数大。代码示例如下,演示了如何安全读取任意位置的可能双字节字符:
public class CodePointDemo {
public static void main(String[] args) {
// 包含普通汉字、ASCII 和补充字符𠮷
String text = "A中𠮷Z";
int len = text.length(); // length 返回 char 数量,这里是 5
for (int i = 0; i < len; ) {
int codePoint = text.codePointAt(i);
// 判断是否为补充字符,决定索引步进量
int charCount = Character.charCount(codePoint);
System.out.println("索引" + i + "的码点:" + codePoint
+ " 字符:" + new String(Character.toChars(codePoint)));
i += charCount; // 重要:按码点占用 char 数前进
}
}
}
上面代码中,text.length() 是 5 而不是 4,因为「𠮷」用了两个 char。循环里用 codePointAt(i) 拿码点,再用 Character.charCount 决定 i 自增 1 还是 2,从而不会把代理对拆开。若错误地写 i++,第二次循环就会单独读到低代理项,输出无意义值。
与类似方法的对比及常见误区
除了 codePointAt,String 还提供 codePointBefore 和 codePointCount。codePointBefore(i) 读取 i-1 处的码点,适合反向遍历;codePointCount(begin, end) 返回两个 char 偏移之间的码点数量,而不是 char 数。很多开发者误以为 substring 能自动按码点截断,其实它只按 char 索引切,可能切断代理对。
另一个误区是在随机访问时用 codePointAt 却不检查边界。虽然 codePointAt 在 index 为最后一个 char 且是孤立高代理项时,会直接返回该代理项值而不抛异常,但业务逻辑上这代表数据损坏。因此解析外部输入前,可配合 Character.isHighSurrogate 与 isLowSurrogate 做校验,确保双字节字符变量完整。
实际工程中的使用建议
在写文本编辑器、协议解析或日志脱敏模块时,凡是涉及「按字符位置」操作,都应优先使用码点 API。例如统计用户昵称显示宽度,不能只看 length(),而应遍历码点并判断是否在 CJK 或补充平面。若要把字符串转成整数码点数组,可结合 offsetByCodePoints 做安全偏移。
当必须和旧代码交互(例如依赖 char 数组的网络库)时,可用 Character.toChars(codePoint) 把码点还原成 char 序列再写入。总之,理解 UTF-16 与码点的区别,熟练使用 codePointAt,是在 Java 中准确读取特定索引位置双字节字符变量的核心手段,也能避免乱码与数组越界等低级故障。
String_codePointAtJava_unicode双字节字符修改时间:2026-08-03 21:27:28