len("你好")返回6是因为go字符串底层是utf-8字节序列,每个汉字占3字节,而len()返回字节数;要获取字符数(rune数)须用utf8.runecountinstring("你好")得2。

为什么 len("你好") 返回 6 而不是 2
Go 字符串底层是字节序列,len() 返回的是字节数,不是字符数。中文、emoji 等 UTF-8 编码字符占多个字节:"你好" 是 6 个字节(每个汉字 3 字节),所以 len("你好") 结果是 6。想得到“人眼可见的字符个数”,必须按 Unicode 码点(rune)计数。
utf8.RuneCountInString 怎么算的
它逐字节扫描字符串,依据 UTF-8 编码规则识别起始字节(如 0b110xxxxx 表示 2 字节字符),跳过后续的 continuation byte(0b10xxxxxx),每识别一个合法的 rune 就计数加一。不解析具体字符内容,只做编码合法性判断和边界划分。
- 遇到非法 UTF-8 序列(如
"\xff\xfe")时,把每个非法字节当作单独的rune(值为0xFFFD),仍计入总数 - 对纯 ASCII 字符串,它仍要逐字节检查是否为
0b10xxxxxx,性能略低于直接len(),但差异极小(纳秒级) - 不会分配新内存,也不拷贝数据,纯只读遍历
什么时候该用 utf8.RuneCountInString,什么时候不该用
需要“用户感知长度”时用它:比如终端显示截断、输入框字数限制、JSON 字段校验最大字符数。但以下情况不要用:
- 做 slice 截取(
s[:n])——字符串索引永远按字节,不是 rune;要用[]rune(s)[:n]转换后再切 - 循环遍历字符——直接用
for _, r := range s,比先算长度再 for i 循环更安全高效 - 性能敏感且确定全是 ASCII(如 HTTP header key)——直接用
len(),避免无谓扫描
常见错误:混淆字节偏移和字符偏移
直接用 utf8.RuneCountInString 结果去当索引会 panic:
str := "a你" n := utf8.RuneCountInString(str) // n == 2 _ = str[n] // panic: index out of range [2] with length 4
因为 str[2] 是第二个汉字的中间字节,非法访问。真正想取第 2 个字符,得:
- 转成
[]rune:r := []rune(str); r[1] - 或用
utf8.DecodeRuneInString逐步解码获取偏移 - 或用
strings.IndexRune/strings.LastIndexRune找位置
字符边界和字节边界在 UTF-8 里几乎从不重合,这是最常被忽略的底层事实。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











