Go 里的中文字符串:长度、截断和按字节出错的写法

举报
yd_271078175 发表于 2026/11/08 10:41:20 2026/11/08
【摘要】 程序的默认语言是英文,代码里的每一处字符串处理也是。等到界面里出现中文,len()、切片、strings.Title 这些看起来很基础的调用就会开始出错,而且错得很安静:要么截出半个汉字,要么标题只截

程序的默认语言是英文,代码里的每一处字符串处理也是。等到界面里出现中文,len()、切片、strings.Title 这些看起来很基础的调用就会开始出错,而且错得很安静:要么截出半个汉字,要么标题只截了二十个字却显示成一排。

这篇记几个实际会踩的点。

一、len() 是字节数,不是字数

Go 的字符串本质是只读的字节序列。len("你好") 是 6,不是 2,因为一个汉字在 UTF-8 里占三个字节。

想数字符要用 utf8.RuneCountInString,或者 len([]rune(s)):

fmt.Println(len("你好"))                     // 6
fmt.Println(utf8.RuneCountInString("你好"))   // 2

这不是冷知识。所有「最多多少个字」的限制,判断时都该用 rune 数。用字节数判断会出现「英文能写 100 个字符,中文只能写 33 个」的怪现象。

二、截断必须按 rune

下面这行代码在英文下永远正确,在中文下会截出乱码:

short := long[:100]

long[:100] 是按字节切,如果第 100 个字节正好落在某个汉字中间,结果就是一个非法 UTF-8 序列,打印出来是 �,写进 JSON 之后可能直接报错。

正确做法是先转成 rune 切片:

r := []rune(long)
if len(r) > 100 {
    short = string(r[:100])
}

代价是一次内存拷贝。对一个几百字的摘要来说完全可以忽略;如果是大文本反复截断,才需要考虑用 utf8.DecodeRune 边走边数。

三、宽度和长度是两回事

长度是字符数,宽度是显示出来占几列。中文和中文标点在终端里占两列,所以「长度一样」的两行字,对齐结果完全不同:

名字    长度 4
abcdef  长度 6

上面两行如果按长度补空格,第二行会比第一行长——因为第一行四个汉字实际占了八列。

终端对齐需要按显示宽度算,标准库没有直接支持,golang.org/x/text/width 或者 go-runewidth 这类包能做。项目不引依赖的话,退一步的做法是:手动给含中文的列留固定宽度,或者干脆别用表格。

四、拼接用 Builder,别用 +

少量拼接用 + 没问题,循环里用 + 会不断分配新字符串,复杂度是平方级的:

var b strings.Builder
for _, s := range parts {
    b.WriteString(s)
    b.WriteString("\n")
}
result := b.String()

strings.Builder 还会避免拷贝转换,b.WriteString 传字符串不需要先转成 []byte。这类细节在小工具里不关键,但写成习惯没有坏处。

五、大小写、比较和规范化

strings.ToUpper、strings.EqualFold 对中文不起作用,这没问题。真正的坑在别处:

中文输入法打出的全角字母、数字、标点和半角是不同的码点。用户输入的 A 和 A 不相等,123 和 123 也不相等。如果要做「按名称查找」,得先做一次全角转半角。

中文标点同理:, 和 ,、: 和 :。做关键词匹配前统一一下,能少很多「明明输入了却搜不到」的问题。

繁简是另一个维度。strings 包完全不处理,需要专门的转换表。这一条除非明确需要,否则不建议自己写。

六、小结

四条规则:

1. 数字数用 rune,不用字节; 2. 截断先转 []rune,别直接切字符串; 3. 终端对齐按显示宽度算,不是字符数; 4. 全角半角、中文标点在比较前先统一。

这些都不会在英文环境下暴露,所以测试和自测的时候,记得拿一段中文去试——哪怕只是把测试数据里的 hello 换成 你好。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。