字符串与文本处理
包总览
| 包 | 核心函数 | 用途 |
|---|
strings | Contains, Split, Join, Replace, Trim | 字符串操作 |
strconv | Atoi, Itoa, ParseFloat, FormatFloat | 类型转换 |
bytes | Contains, Split, Join, Buffer | []byte 操作 |
regexp | Compile, Match, Find, ReplaceAllString | 正则匹配 |
unicode | IsDigit, IsLetter, IsSpace, ToUpper | Unicode 判断 |
unicode/utf8 | RuneCountInString, EncodeRune, DecodeRune | UTF-8 编码 |
text/template | New, Parse, Execute | 模板渲染 |
html/template | 同上 + HTML 转义 | HTML 安全模板 |
strings 包
查找与判断
s := "Hello, World!"
// 包含
strings.Contains(s, "World") // true
strings.ContainsAny(s, "abc") // true(包含 a/b/c 任一)
strings.HasPrefix(s, "Hello") // true
strings.HasSuffix(s, "!") // true
// 查找位置
strings.Index(s, "World") // 7(第一次出现的位置)
strings.LastIndex(s, "l") // 10(最后一次出现)
strings.IndexByte(s, ',') // 5
strings.IndexAny(s, "aeiou") // 1(第一个元音位置)
// 计数
strings.Count(s, "l") // 3
// 大小写
strings.ToUpper(s) // "HELLO, WORLD!"
strings.ToLower(s) // "hello, world!"
strings.ToTitle(s) // "HELLO, WORLD!"
strings.Title("hello world") // "Hello World"(deprecated,用 cases 替代)
分割与拼接
// 分割
strings.Split("a,b,c", ",") // ["a", "b", "c"]
strings.SplitN("a,b,c", ",", 2) // ["a", "b,c"]
strings.SplitAfter("a,b,c", ",") // ["a,", "b,", "c"]
strings.Fields(" hello world ") // ["hello", "world"](按空白分割)
// 自定义分割函数
fields := strings.FieldsFunc("a,b;c|d", func(r rune) bool {
return r == ',' || r == ';' || r == '|'
}) // ["a", "b", "c", "d"]
// 拼接
strings.Join([]string{"a", "b", "c"}, "-") // "a-b-c"
替换与修剪
// 替换
strings.Replace("aaa", "a", "b", 2) // "bba"(替换2次)
strings.ReplaceAll("aaa", "a", "b") // "bbb"
strings.Replace("aaa", "a", "b", -1) // "bbb"(-1 = 全部)
// 修剪(首尾)
strings.TrimSpace(" hello ") // "hello"
strings.Trim("??hello??", "?") // "hello"
strings.TrimLeft("??hello", "?") // "hello"
strings.TrimRight("hello??", "?") // "hello"
strings.TrimPrefix("hello world", "hello ") // "world"
strings.TrimSuffix("file.txt", ".txt") // "file"
// 重复
strings.Repeat("ab", 3) // "ababab"
strings.Builder(高效拼接)
// ❌ 低效:每次 += 创建新字符串
s := ""
for i := 0; i < 1000; i++ {
s += strconv.Itoa(i) // 每次复制整个字符串
}
// ✓ 高效:strings.Builder
var b strings.Builder
for i := 0; i < 1000; i++ {
b.WriteString(strconv.Itoa(i))
}
result := b.String()
// 预分配
b := strings.Builder{}
b.Grow(4096) // 预分配容量,避免扩容
// Builder 方法
b.WriteString("hello")
b.WriteByte(' ')
b.WriteRune('世')
b.Write([]byte("界"))
fmt.Println(b.String()) // hello 世界
fmt.Println(b.Len()) // 字节数
b.Reset() // 重置
strings.Reader
r := strings.NewReader("hello world")
buf := make([]byte, 5)
n, _ := r.Read(buf) // n=5, buf="hello"
// 可作为 io.Reader 传递给其他函数
io.Copy(os.Stdout, r) // 输出剩余内容
strings.Cut(Go 1.18+)
// Cut:分割为 before/after 两部分
before, after, found := strings.Cut("key=value", "=")
// before="key", after="value", found=true
before, after, found := strings.Cut("no-separator", "=")
// before="no-separator", after="", found=false
// CutPrefix / CutSuffix(Go 1.20+)
s, found := strings.CutPrefix("Hello, World!", "Hello, ")
// s="World!", found=true
s, found := strings.CutSuffix("file.txt", ".txt")
// s="file", found=true
strconv 包
// 字符串 → 数字
n, err := strconv.Atoi("42") // 42
n, err := strconv.ParseInt("42", 10, 64) // base=10, bitSize=64
n, err := strconv.ParseUint("42", 10, 64)
f, err := strconv.ParseFloat("3.14", 64)
b, err := strconv.ParseBool("true") // true
// 数字 → 字符串
s := strconv.Itoa(42) // "42"
s := strconv.FormatInt(42, 10) // "42"(十进制)
s := strconv.FormatInt(255, 16) // "ff"(十六进制)
s := strconv.FormatFloat(3.14, 'f', 2, 64) // "3.14"
s := strconv.FormatBool(true) // "true"
// Quote:字符串加引号(转义处理)
s := strconv.Quote(`Hello "World"`) // `"Hello \"World\""`
s := strconv.QuoteToASCII("Hello 世界") // `"Hello \u4e16\u754c"`
// Append 系列(避免分配)
buf := []byte("result: ")
buf = strconv.AppendInt(buf, 42, 10) // buf = "result: 42"
bytes 包
// bytes 包的 API 几乎和 strings 一一对应
// 区别:操作 []byte 而非 string
b := []byte("Hello, World!")
bytes.Contains(b, []byte("World")) // true
bytes.Index(b, []byte("World")) // 7
bytes.Split(b, []byte(", ")) // [["Hello"] ["World!"]]
// bytes.Buffer:可变缓冲区
var buf bytes.Buffer
buf.WriteString("hello")
buf.WriteByte(' ')
buf.WriteString("world")
result := buf.String() // "hello world"
// bytes.Reader
r := bytes.NewReader([]byte("hello"))
io.Copy(os.Stdout, r)
// bytes.Equal:比较 []byte(时间恒定,防时序攻击)
// 用于 HMAC 比较等安全场景
if bytes.Equal(mac1, mac2) {
// 相等
}
// bytes.Compare:字典序比较
bytes.Compare([]byte("abc"), []byte("abd")) // -1
bytes.Buffer vs strings.Builder
| 对比 | bytes.Buffer | strings.Builder |
|---|
| 读操作 | ✓ Read/ReadByte/ReadString | ✗ 只能写 |
| 写操作 | ✓ | ✓ |
| 转字符串 | .String() | .String() |
| 性能 | 稍慢(有额外 bookkeeping) | 更快 |
| 适用 | 需要读写双向操作 | 只需拼接字符串 |
regexp 包
import "regexp"
// 编译正则(编译失败返回 error)
re := regexp.MustCompile(`\d+`) // Must 版本:失败 panic
re := regexp.MustCompile(`^(\w+)@(\w+)\.(\w+)$`)
// 匹配
re.MatchString("hello123") // true
re.Match([]byte("hello123")) // true
// 查找
re.FindString("hello 123 world 456") // "123"
re.FindAllString("hello 123 world 456", -1) // ["123", "456"]
re.FindStringIndex("hello 123") // [6, 9](匹配位置)
// 捕获组
re := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`)
match := re.FindStringSubmatch("user@example.com")
// match[0] = "user@example.com"(完整匹配)
// match[1] = "user"
// match[2] = "example"
// match[3] = "com"
re.FindAllStringSubmatch("a@b.c d@e.f", -1)
// [["a@b.c" "a" "b" "c"] ["d@e.f" "d" "e" "f"]]
// 替换
re := regexp.MustCompile(`\d+`)
re.ReplaceAllString("abc123def456", "N") // "abcNdefN"
re.ReplaceAllStringFunc("abc123", func(s string) string {
n, _ := strconv.Atoi(s)
return strconv.Itoa(n * 2)
}) // "abc246"
// 使用 ${name} 或 $1 引用捕获组
re := regexp.MustCompile(`(\w+)@(\w+)`)
re.ReplaceAllString("user@domain", "$2/$1") // "domain/user"
re.ReplaceAllString("user@domain", "${2}/${1}") // 同上,更清晰
// 分割
re.Split("a1b2c3d", `\d`) // ["a" "b" "c" "d"]
正则性能注意
// ✅ 编译一次,复用多次(放在包级变量)
var emailRe = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
func isValidEmail(email string) bool {
return emailRe.MatchString(email)
}
// ❌ 每次调用都编译
func isValidEmail(email string) bool {
re := regexp.MustCompile(`...`) // 每次都重新编译!
return re.MatchString(email)
}
| 操作 | 性能(纳秒) | 说明 |
|---|
MatchString | ~100ns | 简单匹配 |
FindStringSubmatch | ~200ns | 带捕获组 |
ReplaceAllString | ~300ns | 替换 |
编译 Compile | ~5000ns | 只需一次 |
正则比字符串操作慢 10-100 倍。能用 strings 解决的不要用 regexp。
text/template
import "text/template"
// 定义模板
const tmpl = `Hello, {{.Name}}!
You have {{.Count}} messages.
{{if .IsAdmin}}Admin privileges active.{{end}}
{{range .Items}}- {{.}}
{{end}}`
// 数据
type Data struct {
Name string
Count int
IsAdmin bool
Items []string
}
data := Data{
Name: "小徐",
Count: 5,
IsAdmin: true,
Items: []string{"task1", "task2", "task3"},
}
// 渲染
t := template.Must(template.New("msg").Parse(tmpl))
err := t.Execute(os.Stdout, data)
// 输出:
// Hello, 小徐!
// You have 5 messages.
// Admin privileges active.
// - task1
// - task2
// - task3
模板语法
{{.}} // 当前值
{{.Field}} // 字段访问
{{.Method}} // 方法调用
{{index .Map "key"}} // map/数组索引
{{if .Condition}}...{{end}}
{{if .Condition}}...{{else}}...{{end}}
{{if eq .A .B}}...{{end}} // 条件判断
{{range .Items}}
{{.}} // range 内的 . 是当前元素
{{end}}
{{range $i, $v := .Items}}
{{$i}}: {{$v}}
{{end}}
{{with .Field}} // with 改变上下文
{{.SubField}}
{{end}}
{{pipeline | func2}} // 管道:上一个的输出作为下一个的输入
{{printf "%d" .Count}} // 内置函数
{{/* 注释 */}}
内置函数
| 函数 | 示例 | 说明 |
|---|
and | {{and .A .B}} | 逻辑与 |
or | {{or .A .B}} | 逻辑或 |
not | {{not .Flag}} | 逻辑非 |
eq | {{eq .A .B}} | 等于 |
ne | {{ne .A .B}} | 不等于 |
lt le gt ge | {{lt .A .B}} | 比较 |
len | {{len .Items}} | 长度 |
index | {{index .Map "key"}} | 索引 |
printf | {{printf "%d" .N}} | 格式化 |
html | {{html .Str}} | HTML 转义 |
js | {{js .Str}} | JS 转义 |
urlquery | {{urlquery .Str}} | URL 编码 |
自定义函数
funcMap := template.FuncMap{
"toUpper": strings.ToUpper,
"formatTime": func(t time.Time) string {
return t.Format("2006-01-02 15:04:05")
},
"truncate": func(s string, n int) string {
if len(s) <= n {
return s
}
return s[:n] + "..."
},
}
t := template.New("mytemplate").Funcs(funcMap)
t, _ = t.Parse(`{{.Name | toUpper}} created at {{.Time | formatTime}}`)
t.Execute(os.Stdout, data)
html/template
import "html/template"
// html/template 自动做 HTML 转义,防止 XSS
const tmpl = `<div>Hello, {{.Name}}!</div>`
t := template.Must(template.New("page").Parse(tmpl))
t.Execute(os.Stdout, struct{ Name string }{Name: "<script>alert('xss')</script>"})
// 输出:<div>Hello, <script>alert('xss')</script>!</div>
// 脚本被转义,不会执行
// 需要输出原始 HTML(确保已安全处理)
template.HTML(`<div>raw html</div>`) // 标记为安全 HTML
template.CSS(`body { color: red; }`) // 标记为安全 CSS
template.JS(`console.log("safe")`) // 标记为安全 JS
template.URL(`https://example.com`) // 标记为安全 URL
字符串编码处理
import "unicode/utf8"
import "unicode"
// 字符串长度
s := "Hello 世界"
len(s) // 12(字节数!)
utf8.RuneCountInString(s) // 8(字符数)
// 遍历字符
// ❌ 按字节遍历(中文会乱)
for i := 0; i < len(s); i++ {
fmt.Printf("%c", s[i]) // 乱码
}
// ✓ 按 rune 遍历
for i, r := range s {
fmt.Printf("%d: %c\n", i, r)
}
// rune 与 string 转换
r := '世' // rune (int32)
s := string(r) // "世"
runes := []rune("Hello 世界") // []rune{72, 101, 108, 108, 111, 32, 19990, 30028}
s := string(runes) // "Hello 世界"
// 编码/解码
buf := make([]byte, 4)
n := utf8.EncodeRune(buf, '世') // n=3, buf=[0xe4 0xb8 0x96]
r, size := utf8.DecodeRune(buf) // r='世', size=3
// 验证 UTF-8
utf8.ValidString("hello") // true
utf8.ValidString("\xff\xfe") // false
Unicode 判断
unicode.IsDigit('5') // true
unicode.IsLetter('a') // true
unicode.IsSpace(' ') // true
unicode.IsUpper('A') // true
unicode.IsLower('a') // true
unicode.IsPunct(',') // true
unicode.IsPrint('a') // true
unicode.ToUpper('a') // 'A'
unicode.ToLower('A') // 'a'
unicode.ToTitle('a') // 'A'