Menu

Golang 字符串:Split、Join、Contains、Replace 等用法

在 Go 中处理字符串:strings 包(Split、Join、Contains、Replace、Fields、TrimSpace、ToUpper),用 strings.Builder 高效拼接字符串,多行原始字符串,不可变性,以及为什么 len 统计的是字节数。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

Go 字符串是一个不可变的字节序列,通常是 UTF-8 文本。运算符直接作用于字符串本身(+、==、<、索引、切片),其他功能都在标准库的 strings 包里。

分割字符串

函数按什么分割说明
Split(s, sep)每个 sep保留空的部分;Split("", ",") 返回 [""],即一个空字符串
SplitN(s, sep, n)最多 n 个部分最后一部分包含剩下的全部内容
SplitAfter(s, sep)每个 sep每一部分都保留分隔符
Fields(s)连续的空白从不返回空的部分
FieldsFunc(s, f)使 f 为真的字符自定义分隔符
Cut(s, sep)只在第一个 sep 处返回前半部分、后半部分和是否找到(Go 1.18)

Split("", ",") 这种情况经常让人踩坑:空输入得到的是长度为 1 的切片,而不是 0。分割用户输入时,用 Fields,或者先检查 s == "",就能避免多出一个空元素。

要分割 key=value、user@host 这类成对的值,strings.Cut 是最简洁的办法。Go 1.24 还加入了迭代器版本 strings.SplitSeq 和 strings.FieldsSeq,它们逐个产出各部分,不需要构建切片。

查找

函数返回
Contains(s, sub)sub 出现在 s 中时返回 true
ContainsAny(s, chars)chars 中任意一个字符出现时返回 true
HasPrefix(s, p) / HasSuffix(s, p)s 以 p 开头或结尾时返回 true
Index(s, sub)第一个匹配的字节位置,找不到时返回 -1
LastIndex(s, sub)最后一个匹配的字节位置,找不到时返回 -1
Count(s, sub)不重叠匹配的个数
EqualFold(a, b)不区分大小写的相等比较

除了 EqualFold,所有查找都区分大小写。要不区分大小写地查找,先把两边都转成小写,或者在比较整个字符串时用 EqualFold。

转换

有一个陷阱:Trim、TrimLeft 和 TrimRight 接收的是一个字符集合,不是前缀。strings.TrimLeft("abcab", "ab") 会去掉开头所有的 a 和 b,返回 "cab"。要去掉确切的前缀或后缀,用 TrimPrefix 和 TrimSuffix。

拼接与 strings.Builder

+ 拼接字符串,fmt.Sprintf 把多个值格式化成一个字符串。因为字符串不可变,两者每次都会分配一个新字符串。操作次数少时没问题,在循环里就会变慢,因为每次 += 都要复制之前拼好的全部内容。这时改用 strings.Builder:

零值的 strings.Builder 可以直接使用。它实现了 io.Writer,所以 fmt.Fprintf 可以直接写入它。如果大致知道结果有多大,先调用 b.Grow(n)。写入之后不要复制 Builder;请传指针。

多行字符串与原始字符串

Go 有两种字符串字面量:

双引号字符串会处理转义序列(\n、\t、\"、\\、\u00e9),而且必须写在一行内。反引号括起来的原始字符串按字面意思保留所有内容,包括换行,所以多行字符串、正则表达式、JSON 和 Windows 路径都用它来写。原始字符串里不能包含反引号,其中的回车符会被丢弃。注意上面的查询以一个换行开头,因为字面量从开头的反引号之后立刻开始。

长度、索引与子串

len(s) 是字节数。s[i] 是一个字节,s[i:j] 是按字节位置截取的子串:

ï 和 \u00e9 各占两个字节,所以这个字符串有 12 个字节、10 个字符。只要位置来自 strings.Index 之类的函数,按字节截取就是安全的,因为它们返回的字节偏移量都落在字符边界上。要取“前 n 个字符”,先转换成 []rune。rune 和字节一节会详细讲解 UTF-8。

字符串不可变

你不能原地修改某个字节:s[0] = 'H' 会编译出错(cannot assign to s[0] (neither addressable nor a map index expression))。strings 中的每个函数都返回一个新字符串。要修改内容,先转换成 []byte 或 []rune,修改切片,再转换回来:

b := []byte("hello")
b[0] = 'H'
s := string(b) // "Hello"

不可变也是子串开销很小的原因:s[2:5] 与 s 共享内存,而不是复制一份。反过来,一个巨大字符串的小子串会让整个巨大字符串一直留在内存里。如果要保留大输入中的一小段,用 strings.Clone(Go 1.20)。

比较字符串

== 和 != 比较内容,<、> 逐字节比较,对 ASCII 来说就是字母顺序。strings.Compare 也存在,但很少需要;对 []string 调用 slices.Sort 用的就是 <。要按地区规则给非英文文本排序,用 golang.org/x/text/collate。

转换其他类型

数字通过 strconv.Itoa、strconv.FormatFloat 或 fmt.Sprintf 转成字符串,字符串通过 strconv.Atoi 和 strconv.ParseFloat 转成数字。不要写 string(42):它得到的是码点为 42 的字符 "*"。类型转换一节有完整的对照表。

常见问题

Go 中如何分割字符串?

用 strings.Split(s, sep):strings.Split("a,b,c", ",") 返回 ["a" "b" "c"]。要按任意长度的空白分割并去掉空的部分,用 strings.Fields(s)。strings.SplitN 限制分割出的部分数量,strings.Cut 只在第一个分隔符处切一次。

Go 中如何写多行字符串?

用反引号括起来的原始字符串字面量:

query := `SELECT id, name
FROM users
WHERE active = true`

反引号之间的所有内容都按字面意思保留,包括换行和反斜杠。原始字符串里不能包含反引号。

Go 中如何判断字符串是否包含子串?

strings.Contains(s, substr) 返回一个 bool。相关函数:strings.HasPrefix、strings.HasSuffix、strings.Index(返回位置,找不到时返回 -1)、strings.ContainsAny(是否包含一组字符中的任意一个),以及不区分大小写比较相等的 strings.EqualFold。

Go 中拼接字符串最快的方法是什么?

只拼几个字符串时,+ 就够了。在循环里构建字符串时,用 strings.Builder:反复调用 WriteString,最后调用一次 String()。在循环里反复 += 每次都会复制整个字符串,字符串越长就越慢。要用分隔符连接一个切片,用 strings.Join。

为什么 len 返回的字符串长度不对?

len(s) 返回的是字节数,不是字符数。Go 字符串使用 UTF-8 编码,ASCII 以外的字符占 2 到 4 个字节,所以 len("héllo") 是 6。要得到 Unicode 码点的数量,用 utf8.RuneCountInString(s)。

Coddy 编程语言插图

和 Coddy 一起学编程

开始