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

Java中表情🤣的String.length()与substring()不符合预期问题

Java中表情字符串substring异常问题解析

问题背景

尝试打印UTF-8字符串前30个字符时,发现Java的String.substring()返回异常内容。聚焦到表情符号🤣(U+1F923),预期它的String长度为1,且substring()不会截断它,但Java认为其长度为2,截取前1个字符得到“?”。确认🤣的UTF-8编码为0xF0 0x9F 0xA4 0xA3,测试Java 11.0.20.1和19.0.2版本均存在此问题,而Python 3表现符合预期。

Java测试代码

import java.nio.charset.StandardCharsets;

public class Foo {

  public static void main(String[] args){
    String str = "🤣";
    // 表情"ROLLING ON THE FLOOR LAUGHING"的UTF-8字节
    byte[] raw = {(byte)0xf0, (byte)0x9f, (byte)0xa4, (byte)0xa3};
    String str2 = new String(raw, StandardCharsets.UTF_8);
    System.out.println(str.equals(str2));
    System.out.println(str.length());
    System.out.println(str.substring(0,1));
  }
}

预期输出

true
1
🤣

实际输出

true
2
?

Python 3测试结果

$ python3 -c 'print(len("🤣"))'
1

$ python3 -c 'print("🤣"[0])'
🤣

问题原因

Java的String类底层基于UTF-16编码实现,而🤣(U+1F923)属于Unicode增补平面字符(代码点大于U+FFFF),这类字符在UTF-16中需要用**两个16位代码单元(代理对)**表示:

  • String.length()返回的是UTF-16代码单元的数量,而非Unicode字符(代码点)的数量,因此🤣的length()结果为2;
  • substring(int beginIndex, int endIndex)按UTF-16代码单元截取,截取0到1时只取了代理对的第一个单元,这是无效的单独代理字符,最终显示为“?”。

Python 3的字符串默认按Unicode代码点处理,len()返回字符数,索引访问也按代码点取完整字符,因此表现符合预期。

正确处理方式

如果需要按Unicode字符(代码点)截取字符串,可采用以下两种方法:

方法1:通过codePoints遍历处理

public static String substringByCodePoints(String str, int start, int end) {
    int[] codePoints = str.codePoints().toArray();
    if (end > codePoints.length) end = codePoints.length;
    return new String(codePoints, start, end - start);
}

调用示例:

String str = "🤣";
System.out.println(substringByCodePoints(str, 0, 1)); // 输出🤣

方法2:用Character.offsetByCodePoints计算索引

String str = "🤣";
int endIndex = Character.offsetByCodePoints(str, 0, 1);
System.out.println(str.substring(0, endIndex)); // 输出🤣

该方法可根据代码点数量计算对应的UTF-16索引,避免截断代理对。


内容的提问来源于stack exchange,提问作者Peter V. Mørch

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 09:42:39