应按 unicode 码点截取而非 char 索引,因 java string 使用 utf-16 编码,增补字符(如 emoji)占两个 char;需用 codepointcount 和 offsetbycodepoints 安全计算索引,再 substring。

Java 中截取含中文和 Emoji 的字符串,关键在于正确处理 Unicode 字符的编码单位。Java 的 String 内部使用 UTF-16 编码,而部分 Emoji(如 ?、??、?)属于**增补字符(Supplementary Characters)**,需用两个 char(即一个代理对,surrogate pair)表示。若直接用 substring(int beginIndex, int endIndex) 或基于 length() 计算索引,极易在代理对中间截断,导致乱码(如显示 )或异常。
按 Unicode 码点(Code Point)截取,而非 char 索引
String.length() 返回的是 UTF-16 编码单元数(char 个数),不是真实字符数。正确做法是遍历码点,统计“用户感知的字符”个数:
- 使用
String.codePointCount(int beginIndex, int endIndex)获取指定范围内的码点数量 - 使用
String.offsetByCodePoints(int start, int codePointOffset)安全计算目标结束位置 - 再用
substring()截取(此时起止索引已避开代理对内部)
示例:截取前 5 个“可视字符”(含中文、单 Emoji、组合 Emoji):
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
public static String safeSubstring(String str, int charCount) {
if (str == null || charCount
注意组合 Emoji 和 ZWJ 序列
像 ??(程序员)、❤️?(心动火焰)这类是通过零宽连接符(ZWJ, U+200D)连接的序列,它们是一个逻辑字符,但由多个码点组成(如 ?? = ? + U+200D + ?,共 3 个码点)。codePointCount() 会将其计为多个码点,而非 1 个。
若需按“视觉字形”而非“码点”截取(更贴近用户预期),需借助 ICU4J 或 Android 的 BreakIterator(Java 标准库不原生支持图形簇拆分)。简单场景下可接受按码点截取,因绝大多数组合 Emoji 在主流平台渲染为单个图标,且截断风险低于代理对截断。
避免常见错误写法
- ❌
str.substring(0, 10):假设每个字符占 1 个char,遇到 Emoji 直接崩 - ❌
str.toCharArray().length > 10 ? str.substring(0,10) : str:本质同上,toCharArray()暴露 UTF-16 细节 - ❌ 用
str.getBytes(StandardCharsets.UTF_8).length判断长度:字节长度 ≠ 字符长度,无法用于截取索引
补充:校验与容错
截取后可用 String#isBlank() 或正则 \p{So}(符号-其他,覆盖大部分 Emoji)简单验证是否残留无效代理单元。更健壮的做法是在截取前先 normalize(如 NFC),并确保源字符串本身未损坏:
String normalized = Normalizer.normalize(str, Normalizer.Form.NFC); return safeSubstring(normalized, charCount);
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










