讲解
Go 的字符串是「只读的字节序列」,底层按 UTF-8 编码。这意味着两件事:len(s) 返回的是字节数而不是字符数——"你好" 的 len 是 6;用下标 s[0] 取到的是一个字节,对中文来说只是某个汉字编码的三分之一,直接按下标处理中文必然出错。
正确处理文本的单位是 rune:它是 int32 的别名,代表一个 Unicode 码点,可以通俗理解为「一个字符」。把字符串转成 []rune 就能按字符切片、取长度;用 for range 遍历字符串时,Go 会自动按 UTF-8 解码,每次迭代给出一个 rune 和它的字节下标,这是遍历中文文本的标准姿势。
strings 包提供日常所需的全部字符串工具:Contains 判断包含、Split 切分、Join 拼接、ToUpper/ToLower 大小写、TrimSpace 去两端空白、Replace 替换。需要高频拼接大量字符串时(比如循环里累加),用 strings.Builder 而不是 +=,前者避免反复分配内存,性能差距在循环上千次后非常明显。
示例
字节数与字符数的区别,以及按 rune 遍历中文:
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
s := "你好,Go"
fmt.Println("字节数:", len(s)) // 11
fmt.Println("字符数:", utf8.RuneCountInString(s)) // 5
for i, r := range s {
fmt.Printf("字节下标 %d: %c\n", i, r)
}
}
strings 包的常用函数:
package main
import (
"fmt"
"strings"
)
func main() {
s := "go 语言入门"
fmt.Println("包含「语言」?", strings.Contains(s, "语言"))
fmt.Println(strings.ToUpper("hello go"))
fmt.Println(strings.Split("a,b,c", ","))
fmt.Println(strings.Join([]string{"你", "好"}, "-"))
fmt.Println(strings.Repeat("Go", 3))
fmt.Println("去空白:", "["+strings.TrimSpace(" hi ")+"]")
}
循环拼接用 strings.Builder:
package main
import (
"fmt"
"strings"
)
func main() {
var b strings.Builder
for i := 1; i <= 5; i++ {
fmt.Fprintf(&b, "第%d章;", i)
}
fmt.Println(b.String())
}
常见坑
- 用 len() 当字符数:len("你好") 是 6 不是 2。要字符数用 utf8.RuneCountInString,或先转成 []rune 再 len。
- 按下标截取中文:s[0:3] 恰好截出「你」是因为 UTF-8 里它占 3 字节,换成别的字符数就截出乱码。按字符截取请先 []rune(s) 再切片。
- 直接修改字符串:Go 字符串不可变,s[0] = 'a' 编译不过。修改要先转成 []rune 或 []byte,改完再转回 string。
- 大循环里用 += 拼字符串:每次 += 都分配新内存并复制全部内容,数据量大时性能雪崩。循环拼接一律 strings.Builder,或先 append 到 []string 最后 Join。
- 字符串比较用错了工具:== 比较字符串是按字节精确相等且区分大小写。需要忽略大小写用 strings.EqualFold,而不是各自 ToLower 再比(某些 Unicode 字符会有坑)。
小结
字符串是 UTF-8 只读字节序列,len 数字节、rune 数字符、range 自动解码;strings 包加 Builder 覆盖日常处理。下一节进入流程控制:if 条件分支。