len() 返回字符串的utf-8字节数而非字符数,如"你好" len为6但字符数为2;含中文、日文、emoji时直接用len()做下标或循环易越界或截断,如s:="aウb"中s[3]取到的是'ウ'的第二个字节而非完整字符。

len() 返回的是字节数,不是字符数
Go 的 len() 对字符串返回的是 UTF-8 编码后的字节长度,不是人眼看到的“字符个数”。比如 "你好" 有 2 个字符,但 len("你好") 是 6 —— 每个中文占 3 字节。
直接用 len() 做循环边界或切片下标时,容易在含中文、日文、emoji 的字符串里越界或截断错误位置:
-
s := "aウb",len(s)是 5('a'=1 字节,'ウ'=3 字节,'b'=1 字节),但字符总数是 3 -
s[3]取到的是 'ウ' 的第二个字节,不是某个完整字符,string(s[3])会输出乱码 - 用
for i := 0; i 遍历,会把一个中文拆成 3 次迭代,每次拿到单个字节而非 rune
用 []rune 转换获取真实字符数和安全索引
需要按“字符”操作时(比如取第 2 个字符、计算字符长度、做等长校验),必须先转成 []rune:
rs := []rune(s) 把 UTF-8 字节串解码为 Unicode 码点序列,每个元素对应一个视觉上的字符。
-
len(rs)才是真正的字符数,rs[1]是第 2 个字符(不是第 2 个字节) - 切片也安全:
string(rs[0:2])截取前两个字符,不会切断多字节字符 - 注意:转换有开销,不要在热循环里反复做;若只需长度,用
utf8.RuneCountInString(s)更轻量
regexp.FindStringIndex 返回字节偏移,不是字符偏移
正则匹配函数如 regexp.FindStringIndex、regexp.FindIndex 返回的始终是字节位置。在混合 ASCII 和 UTF-8 宽字符的字符串中,这会导致索引错位。
例如:s := "ウィキa",其中 "ウィキ" 是 4 个日文字符,每个占 3 字节,共 12 字节;'a' 实际字符位置是 4,但 FindStringIndex 返回 [12 13] —— 字节偏移,不是字符偏移。
- 跨语言对接(如和 Java/JS 交互)时,对方按字符索引,你按字节索引,直接传
[12 13]会被当成第 13 个字符开始,严重错位 - 修复方法:先用
regexp.FindStringIndex得到字节区间,再用utf8.RuneCountInString(s[:start])把起始字节偏移转成字符偏移 - 或者更稳妥:对整个字符串做
rs := []rune(s),然后在rs上用strings.IndexRune或自定义遍历匹配
range 遍历天然按字符(rune)进行,但只给起始字节索引
for i, r := range s 是 Go 中最安全的字符串遍历方式:它按 rune 迭代,r 是当前字符,i 是该字符在原字符串中的**起始字节位置**。
-
i不是字符序号(从 0 开始的第几个字符),而是字节偏移 —— 所以i可用于切片定位,但不能直接当“第几个字符”用 - 要得到字符序号,需额外计数:
for idx, r := range s { fmt.Printf("字符 %d: %c\n", idx, r) } - 别试图用
i去算“第 n 个字符在哪”,因为中间可能有变长编码;要找第 n 个字符,还是得转[]rune或用utf8.DecodeRuneInString逐步解码
真正麻烦的地方不在怎么写,而在“什么时候该用字节、什么时候该用字符”——接口契约模糊时(比如第三方库文档没说清楚返回的是字节索引还是字符索引),最容易掉坑里。尤其在处理用户输入、日志提取、前端传参等场景,宽字符一多,len() 和 index 就不再是“所见即所得”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











