因为go语言中字符串底层是utf-8编码的字节数组,直接用整数索引遍历时i代表字节位置而非字符位置,可能导致截断多字节utf-8字符。

字符串遍历时为什么 for i := 0; i 会乱码?
因为 len(s) 返回的是字节数,不是字符数;而中文、emoji 等 UTF-8 字符占多个字节,直接用下标访问 s[i] 拿到的只是某个字节,不是完整字符。
- 例如
s := "你好",len(s)是 6(UTF-8 编码占 3 字节/字符),但只有 2 个 Unicode 字符 -
s[0]是0xe4,单独打印是乱码或不可见控制字符,不是“你” - 想逐个取字符,必须用
for _, r := range s—— 此时r类型是rune,自动按 UTF-8 解码
截取前 N 个字符该用 []rune 还是 []byte?
要看你定义的“N 个字符”是指字节还是语义字符。业务上几乎总是指后者(比如“取用户名前 10 个字”),那就必须转 []rune。
-
s := "Hello 世界?",s[:5]截得是"Hello"(刚好);但s[:6]会切在“世”的 UTF-8 中间字节,导致 panic 或乱码 - 正确做法:
runes := []rune(s); string(runes[:min(10, len(runes))]) - 性能注意:
[]rune(s)是 O(n) 拷贝,高频截取可缓存或改用utf8.RuneCountInString预判长度
string 转 []byte 和 []rune 的本质区别是什么?
前者是零拷贝视图转换(底层共享内存),后者是解码+重新分配内存。
-
[]byte(s)直接把字符串的底层字节切片暴露出来,类型变但内容没动,适合 IO 写入、base64 编码等字节操作 -
[]rune(s)会逐个解析 UTF-8 序列,把每个 Unicode 码点存成一个int32,所以 “你好” 变成[20320 22909],内存占用翻倍(6 字节 → 8 字节) - 误用后果:把
[]rune结果当[]byte传给os.Write会写一堆乱码二进制,根本不是 UTF-8
函数参数该选 byte 还是 rune?看输入来源
不看字符集,看数据性质:原始字节流用 byte,人类可读文本用 rune。
- 网络包解析、文件头读取、加密哈希计算 → 接收
[]byte,避免无谓解码 - 文本搜索(
strings.ContainsRune)、大小写转换(unicode.ToUpper)、正则匹配(regexp默认按 rune)→ 必须用rune - 特别注意:
fmt.Printf("%c", x)中x是byte会打印 ASCII 字符,是rune才能正确显示中文/emoji
最常被忽略的一点:Go 的字符串本身是只读的 UTF-8 字节序列,没有“字符类型”概念。所谓“字符处理”,全是靠你主动选择 byte 还是 rune 视角来决定——选错视角,代码在英文环境跑得通,一遇到中文就崩。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











