You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用String.substring时如何避免返回包含无效Unicode字符的子串

问题根源

Java 的 String.substring 方法是基于 UTF-16 代码单元(即char类型的长度)做截取,而🥦这类位于Unicode扩展平面的字符、以及部分特殊符号,需要占用2个连续的UTF-16代码单元存储。如果截取索引刚好落在单个Unicode字符对应的多个代码单元中间,就会返回孤立的无效代码单元,显示为乱码。

可用解决方案

方案1:基于Unicode码点(Code Point)计算合法截取边界

这是最通用的方案,适用于所有Java版本,核心是使用JDK自带的码点相关API计算合法的截取偏移,避免切到字符中间:

/**
 * 按实际Unicode字符数截取字符串
 * @param str 原字符串
 * @param needCodePointCount 需要截取的Unicode字符个数
 * @return 无无效字符的子串
 */
public static String safeSubstringByCharCount(String str, int needCodePointCount) {
    if (str == null || str.isEmpty() || needCodePointCount <= 0) {
        return "";
    }
    // 取总字符数和需要截取的较小值,避免越界
    int realCount = Math.min(needCodePointCount, str.codePointCount(0, str.length()));
    // 计算对应字符数对应的合法char索引
    int endCharIndex = str.offsetByCodePoints(0, realCount);
    return str.substring(0, endCharIndex);
}

调用示例:对于你给出的测试字符串"🥦_Salade verte",safeSubstringByCharCount(text, 1)会直接返回完整的🥦,无需手动计算emoji占2个char长度。

方案2:按UTF-8字节长度截取的安全方案

如果你的截取需求是适配存储限制,要保证子串的UTF-8字节长度不超过上限,可以用以下方法,截取后自动回退到合法的字符边界:

import java.nio.charset.StandardCharsets;

public static String safeSubstringByUtf8Length(String str, int maxUtf8Bytes) {
    if (str == null || str.isEmpty() || maxUtf8Bytes <= 0) {
        return "";
    }
    byte[] utf8Bytes = str.getBytes(StandardCharsets.UTF_8);
    if (utf8Bytes.length <= maxUtf8Bytes) {
        return str;
    }
    // 回退到合法的UTF-8字符起始位置
    int validEndPos = maxUtf8Bytes;
    while (validEndPos > 0 && (utf8Bytes[validEndPos] & 0xC0) == 0x80) {
        validEndPos--;
    }
    return new String(utf8Bytes, 0, validEndPos, StandardCharsets.UTF_8);
}

方案3:Java 11+ 简化写法

Java 11及以上版本可以直接通过码点流处理,代码更简洁直观:

public static String safeSubstringJava11(String str, int needCodePointCount) {
    if (str == null || str.isEmpty() || needCodePointCount <= 0) {
        return "";
    }
    return str.codePoints()
            .limit(needCodePointCount)
            .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
            .toString();
}

额外注意事项

  • 不要用String.length()的返回值判断实际Unicode字符个数,它返回的是UTF-16代码单元的数量,包含扩展平面字符时和实际字符数不一致,获取实际字符数请用str.codePointCount(0, str.length())。
  • 如果需要处理组合类Unicode字符(比如带肤色的表情、国旗、带声调的拼音等由多个码点组合的单个显示字符),可以搭配BreakIterator类处理字形簇(Grapheme Cluster)边界,避免把单个显示字符切散。

内容的提问来源于stack exchange,提问作者Cheok Yan Cheng

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 03:36:04