强曰为道

与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

18 - 字符串:strings 包、strconv、unicode、正则

18 - 字符串

18.1 字符串基础

Go 的字符串是不可变的 UTF-8 字节序列。

package main

import (
    "fmt"
    "unsafe"
)

func main() {
    s := "Hello, 世界"

    // 字节长度
    fmt.Println("字节数:", len(s)) // 13

    // 字符数(rune 数量)
    fmt.Println("字符数:", len([]rune(s))) // 9

    // 字符串底层是 struct { pointer; length }
    fmt.Println("大小:", unsafe.Sizeof(s)) // 16(两个指针大小)

    // 字符串不可变
    // s[0] = 'h' // 编译错误

    // 创建新字符串
    s2 := "h" + s[1:]
    fmt.Println(s2)
}

字符串 vs []byte vs []rune

func main() {
    s := "Hello, 世界"

    // string → []byte(UTF-8 编码)
    bytes := []byte(s)
    fmt.Println(bytes) // [72 101 108 108 111 44 32 228 184 150 231 149 140]

    // string → []rune(Unicode 码点)
    runes := []rune(s)
    fmt.Println(runes) // [72 101 108 108 111 44 32 19990 30028]

    // []byte → string
    s2 := string(bytes)

    // []rune → string
    s3 := string(runes)

    fmt.Println(s2, s3)
}

18.2 strings 包

查找

import "strings"

func main() {
    s := "Hello, World! Hello, Go!"

    fmt.Println(strings.Contains(s, "Hello"))     // true
    fmt.Println(strings.Count(s, "Hello"))         // 2
    fmt.Println(strings.HasPrefix(s, "Hello"))     // true
    fmt.Println(strings.HasSuffix(s, "Go!"))       // true
    fmt.Println(strings.Index(s, "World"))         // 7
    fmt.Println(strings.LastIndex(s, "Hello"))     // 14
    fmt.Println(strings.ContainsAny(s, "xyz"))     // false
    fmt.Println(strings.ContainsRune(s, '世'))     // false
}

转换

func main() {
    s := "Hello, World!"

    fmt.Println(strings.ToUpper(s))           // HELLO, WORLD!
    fmt.Println(strings.ToLower(s))           // hello, world!
    fmt.Println(strings.Title("hello world")) // Hello World
    fmt.Println(strings.ToTitle(s))           // HELLO, WORLD!

    // 修剪
    s2 := "  Hello, World!  "
    fmt.Println(strings.TrimSpace(s2))              // "Hello, World!"
    fmt.Println(strings.Trim(s2, " "))              // "Hello, World!"
    fmt.Println(strings.TrimLeft(s2, " "))           // "Hello, World!  "
    fmt.Println(strings.TrimRight(s2, " "))          // "  Hello, World!"
    fmt.Println(strings.TrimPrefix("##Hello", "##")) // "Hello"
    fmt.Println(strings.TrimSuffix("Hello##", "##")) // "Hello"
}

分割与连接

func main() {
    // 分割
    s := "a,b,c,d,e"
    fmt.Println(strings.Split(s, ","))         // [a b c d e]
    fmt.Println(strings.SplitN(s, ",", 3))     // [a b c,d,e]
    fmt.Println(strings.SplitAfter(s, ","))    // [a, b, c, d, e]

    // 按空白分割
    s2 := "  hello   world  go  "
    fmt.Println(strings.Fields(s2))            // [hello world go]

    // 连接
    words := []string{"Go", "is", "awesome"}
    fmt.Println(strings.Join(words, " "))      // Go is awesome
    fmt.Println(strings.Join(words, "-"))      // Go-is-awesome

    // 重复
    fmt.Println(strings.Repeat("Go!", 3))      // Go!Go!Go!

    // 替换
    s3 := "Hello World World"
    fmt.Println(strings.Replace(s3, "World", "Go", 1))  // Hello Go World
    fmt.Println(strings.ReplaceAll(s3, "World", "Go"))  // Hello Go Go
}

Builder(高效拼接)

import (
    "strings"
    "testing"
)

// ❌ 效率低(每次 + 创建新字符串)
func concatBad(parts []string) string {
    result := ""
    for _, p := range parts {
        result += p
    }
    return result
}

// ✅ 高效:使用 strings.Builder
func concatGood(parts []string) string {
    var b strings.Builder
    for _, p := range parts {
        b.WriteString(p)
    }
    return b.String()
}

// 预分配容量
func concatBest(parts []string) string {
    totalLen := 0
    for _, p := range parts {
        totalLen += len(p)
    }
    var b strings.Builder
    b.Grow(totalLen) // 预分配
    for _, p := range parts {
        b.WriteString(p)
    }
    return b.String()
}

18.3 strconv 包

import "strconv"

func main() {
    // 字符串 → 整数
    n1, _ := strconv.Atoi("12345")
    n2, _ := strconv.ParseInt("12345", 10, 64) // 十进制,64 位
    n3, _ := strconv.ParseInt("ff", 16, 64)    // 十六进制
    fmt.Println(n1, n2, n3)                     // 12345 12345 255

    // 整数 → 字符串
    s1 := strconv.Itoa(12345)
    s2 := strconv.FormatInt(12345, 10)
    s3 := strconv.FormatInt(255, 16)
    fmt.Println(s1, s2, s3) // 12345 12345 ff

    // 字符串 → 浮点数
    f1, _ := strconv.ParseFloat("3.14", 64)
    fmt.Println(f1)

    // 浮点数 → 字符串
    fmt.Println(strconv.FormatFloat(3.14, 'f', 2, 64))  // 3.14
    fmt.Println(strconv.FormatFloat(3.14, 'e', -1, 64)) // 3.14e+00

    // 字符串 → 布尔
    b1, _ := strconv.ParseBool("true")
    b2, _ := strconv.ParseBool("1")
    b3, _ := strconv.ParseBool("yes")
    fmt.Println(b1, b2, b3) // true true false

    // 布尔 → 字符串
    fmt.Println(strconv.FormatBool(true))  // "true"

    // 更快的转换(无错误处理)
    s := strconv.Itoa(42)
    n, _ := strconv.Atoi("42")
    fmt.Println(s, n)

    // 引用
    fmt.Println(strconv.Quote("Hello\tWorld"))  // "Hello\tWorld"
    fmt.Println(strconv.QuoteRune('中'))         // '中'
}

18.4 unicode 包

import (
    "fmt"
    "unicode"
)

func main() {
    // 字符分类
    fmt.Println(unicode.IsLetter('A'))   // true
    fmt.Println(unicode.IsDigit('5'))    // true
    fmt.Println(unicode.IsSpace(' '))    // true
    fmt.Println(unicode.IsUpper('A'))    // true
    fmt.Println(unicode.IsLower('a'))    // true
    fmt.Println(unicode.IsPunct('!'))    // true
    fmt.Println(unicode.IsChinese('中')) // true

    // 大小写转换
    fmt.Printf("%c\n", unicode.ToUpper('a')) // A
    fmt.Printf("%c\n", unicode.ToLower('A')) // a
    fmt.Printf("%c\n", unicode.ToTitle('a')) // A

    // 统计字符串中的中文字符数
    s := "Hello, 世界! Go 语言"
    chineseCount := 0
    for _, r := range s {
        if unicode.Is(unicode.Han, r) {
            chineseCount++
        }
    }
    fmt.Println("中文字符数:", chineseCount) // 4

    // RuneReader 遍历
    for _, r := range "Hello, 世界" {
        fmt.Printf("%c (%U) ", r, r)
    }
    fmt.Println()
}

18.5 正则表达式

import (
    "fmt"
    "regexp"
)

func main() {
    // 编译正则(推荐编译一次,多次使用)
    re := regexp.MustCompile(`\d+`)

    // 匹配
    fmt.Println(re.MatchString("abc123"))  // true
    fmt.Println(re.MatchString("abc"))     // false

    // 查找
    fmt.Println(re.FindString("abc123def456"))  // 123
    fmt.Println(re.FindAllString("a1b2c3", -1)) // [1 2 3]
    fmt.Println(re.FindStringIndex("abc123"))   // [3 6]

    // 带捕获组
    re2 := regexp.MustCompile(`(\w+)@(\w+)\.(\w+)`)
    match := re2.FindStringSubmatch("[email protected]")
    fmt.Println(match) // [[email protected] user example com]

    // 命名捕获组
    re3 := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
    match2 := re3.FindStringSubmatch("2024-01-15")
    for i, name := range re3.SubexpNames() {
        if i > 0 && name != "" {
            fmt.Printf("%s: %s\n", name, match2[i])
        }
    }

    // 替换
    re4 := regexp.MustCompile(`\s+`)
    fmt.Println(re4.ReplaceAllString("hello   world  go", " "))

    // 替换(使用函数)
    result := re4.ReplaceAllStringFunc("abc123def456", func(s string) string {
        return "[" + s + "]"
    })
    fmt.Println(result)

    // Split
    re5 := regexp.MustCompile(`\d+`)
    parts := re5.Split("abc123def456ghi", -1)
    fmt.Println(parts) // [abc def ghi]
}

常用正则模式

var (
    // 邮箱
    EmailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
    // 手机号(中国)
    PhoneRegex = regexp.MustCompile(`^1[3-9]\d{9}$`)
    // IP 地址
    IPRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`)
    // URL
    URLRegex = regexp.MustCompile(`^https?://[^\s]+$`)
    // 中文
    ChineseRegex = regexp.MustCompile(`[\p{Han}]+`)
)

func validateEmail(email string) bool {
    return EmailRegex.MatchString(email)
}

18.6 strings.Cut(Go 1.18+)

func main() {
    // 字符串分割的首选方式
    before, after, found := strings.Cut("key=value", "=")
    fmt.Println(before, after, found) // key value true

    before, after, found = strings.Cut("no-equal", "=")
    fmt.Println(before, after, found) // no-equal  false

    // 用于解析配置行
    line := "DATABASE_URL=postgres://localhost:5432/mydb"
    key, value, ok := strings.Cut(line, "=")
    if ok {
        fmt.Printf("%s = %s\n", key, value)
    }
}

🏢 业务场景

  1. 数据清洗:strings.Trim/Replace 清洗用户输入
  2. URL 解析:strings.Split + strings.Cut 解析 URL 参数
  3. 日志分析:正则提取日志中的关键信息
  4. 模板渲染:strings.Builder 高效拼接 HTML
  5. 输入验证:正则表达式验证邮箱、手机号等

📖 扩展阅读