string.length() 返回 utf-16 代码单元数而非 unicode 字符数,因 java string 基于 char[] 实现,而增补字符(如 emoji)需两个 char(代理对)表示一个字符;应使用 codepointcount() 获取真实字符数。

Java 中 String.length() 返回的确实是 UTF-16 代码单元(code unit)的数量,不是严格意义上的“字符数”(Unicode character count),这点容易误解,关键在于 Unicode 的编码分层和 Java 字符串的底层实现。
为什么 length() 不等于字符个数?
Java 的 String 内部用 char[] 存储,而 char 是 16 位无符号整数,对应 UTF-16 的一个代码单元。但 Unicode 字符范围远超 65536(即 0x0000–0xFFFF),超出部分(如大部分 emoji、古汉字、音乐符号等)被放在“增补平面”(Supplementary Planes),需要用两个 char(即一个代理对:high surrogate + low surrogate)来表示 —— 这就是两个代码单元,但只代表 1 个 Unicode 字符。
-
"a"→ 1 个代码单元 →length() == 1 -
"中"(U+4E2D)→ 在基本多文种平面(BMP)→ 1 个代码单元 →length() == 1 -
"?"(U+1F60A)→ 在增补平面 → 需要代理对"\uD83D\uDE0A"→ 2 个代码单元 →length() == 2 -
"a?b"→ 实际是'a' + '\uD83D' + '\uDE0A' + 'b'→ 4 个char→length() == 4
怎么真正获取“字符个数”?
如果业务关心的是用户感知的“字符”数量(比如限制输入框最多 10 个表情+文字),不能依赖 length(),而应使用基于代码点(code point)的方法:
-
str.codePointCount(0, str.length()):返回字符串中 Unicode 字符(即代码点)总数 -
str.offsetByCodePoints(0, n):用于安全地按字符索引移动(跳过代理对) -
str.codePointAt(i):获取位置i开始的代码点值(int类型),需配合Character.isSurrogatePair()判断是否为代理对
常见误用场景与建议
很多校验逻辑(如用户名长度限制、数据库字段截断、前端同步显示)若直接用 length(),在遇到 emoji 或生僻汉字时会出错 —— 显示上是一个图标,却被算作两个“字符”,导致提前截断或校验失败。
- 做前端输入限制时,后端也要用
codePointCount校验,避免绕过 - 存入数据库前若需按字符数截断(如 VARCHAR(20)),应按代码点截取,而非简单
substring(0, 20) - 日志或调试时打印
length()和codePointCount()对比,能快速发现增补字符影响
补充说明:它也不是字节数
有人误以为 length() 是字节数,其实完全无关。例如:
-
"abc".getBytes(StandardCharsets.UTF_8).length == 3(UTF-8 下 1 字节/字符) -
"你好".getBytes(StandardCharsets.UTF_8).length == 6(UTF-8 下 3 字节/汉字) - 但两者
.length()都是2(因为是 2 个 BMP 汉字,各占 1 个char)
要获取字节数,必须显式调用 getBytes(...).length,并指定字符集。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











