String.length()返回的是UTF-16编码下的char个数(Unicode代码单元数),不是字节数,也不是真实Unicode字符数;遇增补字符(如emoji)时,一个字符占两个char,length()返回2。

Java里String.length()返回的是字符数还是字节数?
String.length()返回的是Unicode代码单元(code unit)的数量,也就是UTF-16编码下的char个数。它不是字节数,也不是严格意义上的“字符数”——遇到增补字符(如emoji或某些汉字),一个字符可能占两个char(即一个代理对),此时length()会返回2,但实际只表示1个Unicode字符。
常见错误现象:
用str.length()判断是否超过10个“人眼可见字符”,结果在遇到"??"或"?"时出错——它们的length()是2或4,但语义上只是1个字符。
- 需要真实字符数(Unicode code point):改用
str.codePointCount(0, str.length()) - 需要UTF-8字节数(如HTTP头、存储估算):用
str.getBytes(StandardCharsets.UTF_8).length - 处理截断逻辑时,避免直接用
substring(0, 10),应先用offsetByCodePoints定位安全边界
截取指定“视觉长度”的字符串时为什么substring()会乱码?
因为substring(int beginIndex, int endIndex)按char索引切,不识别代理对。若在高代理(surrogate high)和低代理(surrogate low)中间切断,结果字符串末尾会出现''或抛IllegalArgumentException(取决于JDK版本和上下文)。
正确做法是基于代码点操作:
立即学习“Java免费学习笔记(深入)”;
int limit = 10; // 目标字符数 int end = str.offsetByCodePoints(0, Math.min(limit, str.codePointCount(0, str.length()))); String truncated = str.substring(0, end);
- 永远不要用
substring(0, n)替代“取前n个字符”,除非你确定字符串只含BMP字符(ASCII + 常用汉字) - 若需兼容旧JDK(Character.isHighSurrogate(c)手动扫描校验边界
- 数据库或前端传入的字符串若含emoji,这个bug极易在日志、截断展示、分词等环节暴露
中文、emoji、英文混排时怎么统一按“显示宽度”截断?
length()和codePointCount()都只管数量,不管显示宽度。比如中文、emoji通常占2个等宽字符位置,而英文数字占1个——这在终端、表格、固定宽度UI中很关键,但Java标准库不提供内置方案。
可行路径:
- 用第三方库如
java-measure或自定义GraphemeCluster计数(较重) - 轻量级做法:遍历每个
codePoint,查Unicode EastAsianWidth属性(需嵌入简表或调用ICU4J) - 更现实的妥协:对中文/emoji做粗略规则匹配(如
Pattern.compile("[\u4e00-\u9fff\uD83C\uDF00-\uD83D\uDDFF]").matcher(str)),再结合codePointCount加权估算
注意:这类需求往往暴露在导出CSV、生成ANSI着色日志、CLI工具输出等场景,别在通用工具类里硬塞“全量宽度计算”。
性能敏感场景下频繁调用length()有没有开销?
没有。String内部用final char[] value和final int hash存储,length()直接返回value.length,是O(1)且无副作用的字段访问。JVM还会内联优化。
但要注意这些“伪性能陷阱”:
- 写
for (int i = 0; i 没问题,现代JVM会自动hoist,无需手动缓存到局部变量 - 真正慢的是反复调用
codePointCount()或getBytes()——前者要遍历整个字符串,后者触发新数组分配 - 如果循环体本身很轻(如单个字符判断),把
str.length()提到外面反而增加可读性负担,得不偿失
复杂点在于:字符计数逻辑一旦涉及Unicode边界,就不再是纯CPU操作,而是隐含状态机和表查找——这时候瓶颈从来不在length(),而在你选的抽象层级是否匹配真实需求。










