Go字符串切片会崩掉中文,因其底层是字节序列而非字符序列,直接s[0:3]截取UTF-8多字节字符易panic或乱码;须转[]rune按Unicode码点安全截取。

Go 字符串切片会崩掉中文?因为 string 是字节序列不是字符序列
Go 的 string 底层是只读的字节切片,直接用 s[0:3] 截取,遇到 UTF-8 多字节字符(比如中文、emoji)大概率 panic 或截出乱码。这不是 bug,是设计使然——Go 不自动做 Unicode 意义上的“字符”切分。
真正要按“人眼看到的字符数”截取,必须显式解码为 rune(Unicode 码点)。常见错误现象:index out of range、输出显示为 、长度计算和预期不符。
- 使用场景:生成摘要、日志截断、前端展示限长、API 响应字段裁剪
-
len(s)返回字节数,len([]rune(s))才是字符数 - 性能影响:每次
[]rune(s)都会分配新切片并拷贝,高频调用需考虑缓存或复用
用 []rune 安全截取中英文混排字符串
把字符串转成 rune 切片后,再按索引切,就能准确控制字符数量。这是最直白也最常用的做法。
示例:截取前 5 个字符(不管中英文)
立即学习“go语言免费学习笔记(深入)”;
func substrRune(s string, start, end int) string {
runes := []rune(s)
if start > len(runes) {
return ""
}
if end > len(runes) {
end = len(runes)
}
if start > end {
return ""
}
return string(runes[start:end])
}
s := "Hello世界?"
result := substrRune(s, 0, 5) // "Hello世"
- 注意边界检查:避免
start或end超出len([]rune(s)) - 不要对原
string直接下标操作后再转rune,比如string([]rune(s[0:10]))—— 这步s[0:10]可能已破坏 UTF-8 编码 - 如果只是取前 N 个字符,用
string([]rune(s)[:N])更简洁;但需确保N
需要保留字节长度限制?用 utf8.DecodeRuneInString 手动遍历
有些场景要求严格按字节长度截(比如 HTTP header 限制 4KB),又不能破坏最后一个字符的 UTF-8 编码完整性。这时不能转整个 []rune,得边解码边累加字节数。
核心逻辑:逐个读 rune,记录当前总字节数,到临界点前停下。
func truncateByBytes(s string, maxBytes int) string {
if maxBytes <= 0 {
return ""
}
var total int
for i, r := range s {
size := utf8.RuneLen(r)
if total+size > maxBytes {
return s[:i]
}
total += size
}
return s
}
- 返回的是合法 UTF-8 子串,末尾绝不会出现半个中文
- 比
[]rune方案省内存,适合超长文本或内存敏感环境 - 注意:
for i, r := range s中的i是字节偏移,不是 rune 索引,正好用来切
第三方库不是必须的,但 golang.org/x/text/unicode/norm 有隐藏坑
有人想用 norm.NFC.String(s) 先标准化再切,这其实不解决截取问题,反而可能让字符串变长(比如把 “é” 拆成 “e\u0301”),导致字符数突增。
-
golang.org/x/text/runes提供了Substr,但内部仍是转[]rune,没绕过分配开销 - 别依赖
strings.Count统计中文个数来算截断位置——它按字节找,对 UTF-8 不可靠 - 真正复杂的场景(如带控制字符、组合符、双向文本)才需要引入
x/text,日常中英文混排,[]rune足够










