乱码的根本原因是变长编码下直接截断字节数组易切在字符中间;必须显式指定编码,安全截取需向前回退校验字符边界;按字符或码点截取更安全合理。

Java 中按字节数截取字符串时,乱码的根本原因是:在 UTF-8 或 GBK 等变长编码下,一个中文字符可能占 2~4 个字节,直接截断字节数组容易切在字符中间,导致后续解码失败,显示为 或抛出异常。
明确目标编码再操作
不能用 str.getBytes()(无参),它依赖系统默认编码,不可移植。必须显式指定编码:
- UTF-8 下,ASCII 字符占 1 字节,常用汉字占 3 字节,emoji 可能占 4 字节
- GBK 下,汉字固定占 2 字节,英文字母占 1 字节
- 截取前先确认业务场景要求的编码(如 HTTP 接口通常用 UTF-8,老系统可能用 GBK)
安全截取:字节数组 + 向前回退校验
核心思路:把字符串转成目标编码的字节数组 → 截到不超过指定字节数的位置 → 向前检查是否落在合法字符边界上,避免截断多字节字符开头:
- 对 UTF-8:检查字节高位,若末尾字节是
11xxxxxx(起始字节),则前面必须有对应数量的10xxxxxx(续字节);截断点不能停在10xxxxxx上 - 更稳妥做法是:从目标字节位置向前扫描,找到第一个非
10xxxxxx的字节索引,即为合法截止点 - 示例逻辑(UTF-8):取 bytes[0..n],从 n-1 开始往前找,直到 bytes[i] & 0xC0 != 0x80,取 i+1 为有效长度
按字符数截取更简单且通常更合理
如果需求本质是“显示 N 个可见字符”(如前端展示省略),而不是“严格限制报文体积”,优先用 Unicode 字符语义截取:
立即学习“Java免费学习笔记(深入)”;
- str.substring(0, Math.min(n, str.length())) —— 安全,不乱码,但计的是 Java char(可能拆开代理对)
- 要兼容 emoji 和增补平面字符(U+10000 以上),改用 codePoint:
int end = str.offsetByCodePoints(0, Math.min(n, str.codePointCount(0, str.length())));
String result = str.substring(0, end); - 这种方式完全避开字节问题,适用于日志、UI、JSON 响应等多数业务场景
封装建议:区分用途选方法
不要只写一个“按字节截取”工具方法,应按用途提供不同接口:
- forNetworkLimit(String s, int maxBytes, Charset cs):用于协议层字节限长(如 HTTP header、MQ 消息体),内部做 UTF-8/GBK 边界校验
- forDisplay(String s, int maxLength):用于前端展示,按 codePoint 截取,保证视觉完整
- 避免在 service 层处理报文级字节截断——尽量由网关或序列化层统一控制


















