Go字符串不能直接用切片语法按字符截取,因其底层是UTF-8字节序列,s[2:5]截取的是字节而非rune,中文、emoji易被截断导致乱码;安全做法是先转[]rune再切片,如string([]rune(s)[start:end])。

字符串不能直接用切片语法截取,因为它是不可变的字节序列
Go 里 string 底层是只读的 []byte,你写 s[2:5] 看似能跑通,但实际截取的是 UTF-8 字节范围,不是字符(rune)范围。中文、emoji 等多字节字符会被砍断,导致乱码或 panic。
按字节截取(仅适用于纯 ASCII 场景)
如果确定字符串只含 ASCII 字符(比如 base64、hex、英文路径),可直接用切片:
str := "hello world" sub := str[0:5] // "hello" — 安全,每个字符占 1 字节
但一旦混入中文:
str := "你好world" sub := str[0:3] // 得到 "\xe4\xbd\xa0" — 不完整 UTF-8 编码,打印为
- 错误现象:
invalid UTF-8 sequence或显示 - 适用场景:协议头、固定格式二进制标识符、已知 ASCII-only 的 token
- 风险:无显式报错,但后续
range或len([]rune(s))行为异常
按字符(rune)安全截取的正确做法
必须先转成 []rune,再切片,最后转回 string:
立即学习“go语言免费学习笔记(深入)”;
s := "Hello世界?" runes := []rune(s) sub := string(runes[5:8]) // "世界?"
- 为什么这样做:每个
rune对应一个 Unicode 码点,[]rune是真正的“字符数组” - 性能影响:转换涉及内存分配和遍历,对超长字符串(MB 级)要注意 GC 压力
- 边界检查要自己做:
len(runes)才是字符长度,不是len(s) - 常见坑:
runes[100:]可能 panic,务必先判断if start
封装成可复用函数时注意 panic 防御
别裸写 string([]rune(s)[start:end]),容易越界 panic。推荐带校验的辅助函数:
func SubString(s string, start, end int) string {
runes := []rune(s)
if start < 0 {
start = 0
}
if end > len(runes) {
end = len(runes)
}
if start > end {
return ""
}
return string(runes[start:end])
}
- 参数含义:start/end 是 rune 索引,不是字节偏移
- 兼容性:Go 1.0+ 全版本可用,无额外依赖
- 容易被忽略的点:空字符串
""转[]rune得到长度为 0 的切片,不会 panic,但end=1会越界 — 所以必须做end = min(end, len(runes))


















