string是只读字节序列(UTF-8编码),[]rune是Unicode码点切片;前者按字节操作,后者按逻辑字符操作,本质是“字节视图 vs 码点视图”的差异。

Go 里 string 和 []rune 的本质区别是什么
Go 中 string 是只读的字节序列([]byte 的不可变封装),底层是 UTF-8 编码的字节流;而 []rune 是 Unicode 码点切片,每个 rune 对应一个逻辑字符(如中文、emoji、带重音的字母)。这不是“字符串 vs 字符数组”的简单类比,而是“字节视图 vs 码点视图”的根本差异。
常见错误现象:len("??") 返回 4(UTF-8 占 4 字节),但 len([]rune{"??"}) 返回 1;用 for i := range s 遍历 string 得到的是字节索引,不是字符位置。
- 需要按“人眼可见字符”计数、截取、反转时,必须转成
[]rune - 做网络传输、文件 I/O、加密哈希等底层操作时,直接用
string或[]byte更高效 -
string到[]rune的转换是 O(n) 开销,避免在热循环中反复转换
什么时候该用 []byte 而不是 string
当你要修改内容、拼接频繁、或与底层系统交互时,[]byte 是更合适的选择。因为 string 不可变,每次 + 拼接都会产生新分配,而 []byte 可复用底层数组(配合 bytes.Buffer 或预分配切片)。
使用场景举例:构建 HTTP 响应体、解析二进制协议字段、批量处理日志行。
立即学习“go语言免费学习笔记(深入)”;
- 拼接 3 次以上字符串?优先用
bytes.Buffer或预分配[]byte - 需原地修改某个字节(如大小写转换)?用
[]byte,再用string(b)转回(注意:仅当确认是纯 ASCII 或已校验 UTF-8 时才安全) - 正则匹配、
strings.ReplaceAll等函数内部会自动转[]byte,无需手动干预
range 遍历 string 时拿到的到底是索引还是字符
用 for i, r := range s 遍历 string,i 是当前 rune 在原始字节中的起始位置(字节索引),r 是该 rune 的 Unicode 码点值。它不是“第几个字符”,而是“这个字符从第几个字节开始”。
Perl学习手札是台湾perl高手写的一篇文章,特打包为chm版,方便大家阅读。 关于本书 1. 关于Perl 1.1 Perl的历史 1.2 Perl的概念 1.3 特色 1.4 使用Perl的环境 1.5 开始使用 Perl 1.6 你的第一个Perl程序 2. 标量变量(Scalar) 2.1 关于标量 2.1.1 数值 2.1.2 字符串 2.1.3 数字与字符串转换 2.2 使用你自己的变量 2.3 赋值 2.3.1 直接设定 2.3.2 还可以这样 2.4 运算 2.5 变量的输出/输入 2.
容易踩的坑:s[i] 取出的是单个字节,不是字符;若 i 指向一个多字节 UTF-8 字符中间,s[i] 会得到非法字节,强制转 string 可能显示 。
- 要获取第 n 个逻辑字符?先转
[]rune,再索引:r := []rune(s)[n] - 要按字节遍历(比如解析协议头)?用
for i := 0; i ,并用s[i] - 要同时知道字符和位置,且确保位置是码点序号(而非字节偏移)?必须用
for i, r := range []rune(s)
中文、emoji 等多字节字符截断的正确做法
直接用 s[:10] 截取 string 极易破坏 UTF-8 编码,导致末尾出现 。Go 标准库不提供“按字符截断”函数,必须自己处理。
最稳妥的方式是转 []rune 后操作,再转回 string:
s := "Hello世界?"
r := []rune(s)
if len(r) > 5 {
s = string(r[:5]) // 截前 5 个逻辑字符
}
性能敏感场景可手写 UTF-8 解码截断(用 utf8.DecodeRuneInString 循环),但多数业务代码没必要——可读性与正确性优先。
真正容易被忽略的是:strings.TrimSuffix、strings.Split 等函数都基于字节操作,对含 emoji 的字符串仍安全(因为它们不破坏已有编码),但像 strings.Repeat(s, 2) 这种重复操作,只要原 s 是合法 UTF-8,结果也一定是合法的。









