len() 返回字节长度而非字符数,因 go 中 string 是字节序列;utf-8 变长编码致中文等字符占多字节;需用 utf8.runecountinstring() 获取真实 rune 个数。

为什么 len() 不能直接算 UTF-8 字符个数?
因为 Go 的 len() 返回的是字节长度,不是字符(rune)个数。比如中文字符“你好”在 UTF-8 中占 6 个字节(每个汉字 3 字节),但只有 2 个 Unicode 字符。直接用 len("你好") 得到 6,不是你想要的“2”。
- UTF-8 是变长编码:ASCII 字符占 1 字节,中文/emoji 通常占 3 或 4 字节
-
string在 Go 中底层是只读字节序列,len()天然按字节计 - 真正表示“人眼看到的字符数”的,是 rune 的数量,不是 byte 数
utf8.RuneCountInString() 是最直接的解法
标准库 unicode/utf8 提供了专用于统计 UTF-8 字符(rune)个数的函数:utf8.RuneCountInString()。它遍历字符串,按 UTF-8 编码规则拆出每个 rune 并计数。
- 输入是
string,返回int,无 error,安全可靠 - 时间复杂度 O(n),必须扫描全部字节,无法 O(1) —— 这是 UTF-8 的本质限制
- 对空字符串、纯 ASCII、混合 emoji(如 "?中")都正确:前者返回 0,后者返回 2
- 示例:
import "unicode/utf8"<br>fmt.Println(utf8.RuneCountInString("?中")) // 输出 2
别用 len([]rune(s)) 替代,除非你真需要 rune 切片
有人写 len([]rune(s)) 来“转换再数”,这确实能得出正确结果,但代价高得多:
- 会分配新 slice,把整个字符串解码成 rune 数组 —— 内存开销翻倍(尤其大文本)
- 多一次内存拷贝和解码过程,比
RuneCountInString()慢 2–3 倍(实测 10KB 文本) - 如果只是为了计数,完全不需要持有所有 rune,纯属浪费
- 仅当你后续还要遍历、修改或索引某个位置的 rune 时,才值得转
[]rune
注意:RuneCountInString 对 BOM 和控制字符也计数
utf8.RuneCountInString() 统计的是合法 UTF-8 编码的 rune 个数,不做过滤。这意味着:
- 带 UTF-8 BOM(
"\ufeff")的字符串,BOM 算作 1 个 rune - 零宽空格(
\u200b)、软连字符(\u00ad)等不可见控制字符,各算 1 个 - 遇到非法 UTF-8 字节序列(如
"\xff\xfe"),Go 会将其视为单个U+FFFD(replacement char),仍计为 1 个 rune - 如果你要“可视字符数”或“用户感知长度”,得额外过滤或用
unicode.IsPrint()判断
utf8.RuneCountInString();想省事又不怕性能损耗才考虑 []rune;而任何想跳过不可见字符或处理 BOM 的场景,都得自己加逻辑 —— 标准库不会替你做语义判断。golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











