文章

字符串与文本处理

字符串与文本处理

包总览

核心函数用途
stringsContains, Split, Join, Replace, Trim字符串操作
strconvAtoi, Itoa, ParseFloat, FormatFloat类型转换
bytesContains, Split, Join, Buffer[]byte 操作
regexpCompile, Match, Find, ReplaceAllString正则匹配
unicodeIsDigit, IsLetter, IsSpace, ToUpperUnicode 判断
unicode/utf8RuneCountInString, EncodeRune, DecodeRuneUTF-8 编码
text/templateNew, 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.Bufferstrings.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, &lt;script&gt;alert(&#39;xss&#39;)&lt;/script&gt;!</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'