Skip to content

4.7 正则表达式 ​

regexp 包基础 ​

创建正则表达式 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // 方式 1: 编译正则 (推荐,可复用)
    re := regexp.MustCompile(`\d+`)

    // 方式 2: 带错误检查的编译
    re2, err := regexp.Compile(`\d+`)
    if err != nil {
        fmt.Println("编译错误:", err)
        return
    }

    fmt.Println(re, re2)
}

测试匹配 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`^\d{3}-\d{4}$`)

    // MatchString: 测试字符串是否匹配
    fmt.Println(re.MatchString("123-4567"))  // true
    fmt.Println(re.MatchString("12-4567"))   // false

    // Match: 测试 []byte
    fmt.Println(re.Match([]byte("123-4567")))  // true

    // MustMatch: 不返回错误的 Match
    result := regexp.MustCompile(`\d+`).MatchString("abc123")
    fmt.Println(result)  // true
}

查找操作 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`[a-z]+`)
    text := "Hello World 123"

    // Find: 返回第一个匹配的字节 slice
    fmt.Println(string(re.Find([]byte(text))))  // "ello"

    // FindString: 返回第一个匹配的字符串
    fmt.Println(re.FindString(text))  // "ello"

    // FindIndex: 返回第一个匹配的起始和结束位置
    fmt.Println(re.FindIndex([]byte(text)))  // [1 5]

    // FindAll: 返回所有匹配
    fmt.Println(re.FindAllString(text, -1))  // [ello orld]

    // FindAllString: 限制返回数量
    fmt.Println(re.FindAllString(text, 1))  // [ello]

    // FindAllIndex: 返回所有匹配的位置
    fmt.Println(re.FindAllIndex([]byte(text), -1))  // [[1 5] [7 12]]
}

捕获组 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
    text := "日期:2024-01-15 和 2025-12-31"

    // FindStringSubmatch: 返回完整匹配和捕获组
    match := re.FindStringSubmatch("2024-01-15")
    fmt.Println(match)  // [2024-01-15 2024 01 15]

    // FindStringSubmatchIndex: 返回位置
    indices := re.FindStringSubmatchIndex("2024-01-15")
    fmt.Println(indices)  // [0 10 0 4 5 7 8 10]

    // FindAllStringSubmatch: 返回所有匹配和捕获组
    allMatches := re.FindAllStringSubmatch(text, -1)
    fmt.Println(allMatches)
    // [[2024-01-15 2024 01 15] [2025-12-31 2025 12 31]]

    // SubexpNames: 获取命名捕获组名称
    re2 := regexp.MustCompile(`(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})`)
    fmt.Println(re2.SubexpNames())  // [ year month day]
}

替换与分割 ​

替换操作 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    re := regexp.MustCompile(`\d+`)

    // ReplaceAll: 替换所有匹配
    result := re.ReplaceAllString("abc123def456", "X")
    fmt.Println(result)  // "abcXdefX"

    // ReplaceAll: 使用 []byte
    result2 := re.ReplaceAll([]byte("abc123"), []byte("X"))
    fmt.Println(string(result2))  // "abcX"

    // ReplaceAllFunc: 使用函数进行替换
    result3 := re.ReplaceAllStringFunc("abc123def456", func(s string) string {
        return "[" + s + "]"
    })
    fmt.Println(result3)  // "abc[123]def[456]"

    // ReplaceAllString: 使用捕获组引用
    re2 := regexp.MustCompile(`(\w+)\s+(\w+)`)
    result4 := re2.ReplaceAllString("Hello World", "$2 $1")
    fmt.Println(result4)  // "World Hello"

    // 使用命名捕获组引用
    re3 := regexp.MustCompile(`(?P<first>\w+)\s+(?P<last>\w+)`)
    result5 := re3.ReplaceAllString("John Doe", "${last}, ${first}")
    fmt.Println(result5)  // "Doe, John"
}

分割操作 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    // Split: 按正则分割
    re := regexp.MustCompile(`[,\s]+`)
    parts := re.Split("a,b c,d  e", -1)
    fmt.Println(parts)  // [a b c d e]

    // 限制分割数量
    parts2 := re.Split("a,b c,d", 2)
    fmt.Println(parts2)  // [a b c,d]

    // 常见分割模式
    // 按空白字符分割
    re2 := regexp.MustCompile(`\s+`)
    fmt.Println(re2.Split("a  b   c", -1))  // [a b c]

    // 按非单词字符分割
    re3 := regexp.MustCompile(`\W+`)
    fmt.Println(re3.Split("hello,world!test", -1))  // [hello world test]
}

常用正则模式 ​

验证模式 ​

go
package main

import (
    "fmt"
    "regexp"
)

var (
    // 邮箱验证
    emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)

    // 手机号验证 (中国)
    phoneRegex = regexp.MustCompile(`^1[3-9]\d{9}$`)

    // URL 验证
    urlRegex = regexp.MustCompile(`^https?://[^\s]+$`)

    // IP 地址验证 (IPv4)
    ipRegex = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`)

    // 身份证号验证 (简化版)
    idCardRegex = regexp.MustCompile(`^\d{17}[\dXx]$`)

    // 日期格式验证 (YYYY-MM-DD)
    dateRegex = regexp.MustCompile(`^\d{4}-\d{2}-\d{2}$`)

    // 时间格式验证 (HH:MM:SS)
    timeRegex = regexp.MustCompile(`^\d{2}:\d{2}:\d{2}$`)

    // 密码强度 (至少 8 位,包含字母和数字)
    passwordRegex = regexp.MustCompile(`^(?=.*[A-Za-z])(?=.*\d)[A-Za-z\d]{8,}$`)
)

func ValidateEmail(email string) bool {
    return emailRegex.MatchString(email)
}

func ValidatePhone(phone string) bool {
    return phoneRegex.MatchString(phone)
}

func ValidateURL(url string) bool {
    return urlRegex.MatchString(url)
}

func ValidateIP(ip string) bool {
    if !ipRegex.MatchString(ip) {
        return false
    }
    // 进一步验证每段是否在 0-255 范围内
    return true
}

func ValidatePassword(password string) bool {
    return passwordRegex.MatchString(password)
}

func main() {
    fmt.Println("邮箱验证:", ValidateEmail("test@example.com"))
    fmt.Println("手机验证:", ValidatePhone("13800138000"))
    fmt.Println("URL 验证:", ValidateURL("https://example.com"))
    fmt.Println("密码验证:", ValidatePassword("password123"))
}

提取模式 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := `
        <div class="item">
            <h3>商品 1</h3>
            <span class="price">¥199</span>
        </div>
        <div class="item">
            <h3>商品 2</h3>
            <span class="price">¥299</span>
        </div>
    `

    // 提取 HTML 标签内容
    re := regexp.MustCompile(`<h3>(.*?)</h3>`)
    titles := re.FindAllStringSubmatch(text, -1)
    for _, title := range titles {
        fmt.Println("标题:", title[1])
    }

    // 提取价格
    priceRe := regexp.MustCompile(`¥(\d+)`)
    prices := priceRe.FindAllStringSubmatch(text, -1)
    for _, price := range prices {
        fmt.Println("价格:", price[1])
    }

    // 提取属性
    attrRe := regexp.MustCompile(`class="([^"]*)"`)
    attrs := attrRe.FindAllStringSubmatch(text, -1)
    for _, attr := range attrs {
        fmt.Println("class:", attr[1])
    }
}

文本处理模式 ​

go
package main

import (
    "fmt"
    "regexp"
    "strings"
)

// 驼峰转下划线
func CamelToSnake(s string) string {
    re := regexp.MustCompile(`([a-z])([A-Z])`)
    result := re.ReplaceAllString(s, "${1}_${2}")
    return strings.ToLower(result)
}

// 下划线转驼峰
func SnakeToCamel(s string) string {
    re := regexp.MustCompile(`_([a-z])`)
    return re.ReplaceAllStringFunc(s, func(sub string) string {
        return strings.ToUpper(sub[1:])
    })
}

// 去除多余空白
func NormalizeWhitespace(s string) string {
    re := regexp.MustCompile(`\s+`)
    return strings.TrimSpace(re.ReplaceAllString(s, " "))
}

// 提取所有数字
func ExtractNumbers(s string) []string {
    re := regexp.MustCompile(`\d+`)
    return re.FindAllString(s, -1)
}

// 提取所有单词
func ExtractWords(s string) []string {
    re := regexp.MustCompile(`\b\w+\b`)
    return re.FindAllString(s, -1)
}

func main() {
    fmt.Println(CamelToSnake("helloWorld"))      // hello_world
    fmt.Println(SnakeToCamel("hello_world"))     // helloWorld
    fmt.Println(NormalizeWhitespace("a  b   c")) // "a b c"
    fmt.Println(ExtractNumbers("abc123def456"))  // [123 456]
    fmt.Println(ExtractWords("hello, world!"))   // [hello world]
}

正则性能优化 ​

预编译正则 ​

go
package main

import (
    "regexp"
)

// 不好:每次都重新编译
func BadMatch(text string) bool {
    return regexp.MustCompile(`^\d+$`).MatchString(text)
}

// 好:预编译,全局复用
var digitRegex = regexp.MustCompile(`^\d+$`)

func GoodMatch(text string) bool {
    return digitRegex.MatchString(text)
}

使用字面量字符串 ​

go
package main

import "regexp"

func main() {
    // 使用原始字符串字面量 (推荐)
    re1 := regexp.MustCompile(`\d+\.\d+`)

    // 使用普通字符串需要转义
    re2 := regexp.MustCompile("\\d+\\.\\d+")

    // 原始字符串字面量优势:
    // 1. 不需要转义反斜杠
    // 2. 更易读
    // 3. 适合多行模式
    re3 := regexp.MustCompile(`(?m)^\s*#.*$`)

    _ = re1
    _ = re2
    _ = re3
}

非贪婪匹配 ​

go
package main

import (
    "fmt"
    "regexp"
)

func main() {
    text := "<div>hello</div><div>world</div>"

    // 贪婪匹配 (默认)
    greedy := regexp.MustCompile(`<div>.*</div>`)
    fmt.Println(greedy.FindString(text))
    // <div>hello</div><div>world</div>

    // 非贪婪匹配
    nonGreedy := regexp.MustCompile(`<div>.*?</div>`)
    fmt.Println(nonGreedy.FindString(text))
    // <div>hello</div>

    // 使用 [^>]* 代替.*? (更高效)
    efficient := regexp.MustCompile(`<div>[^>]*</div>`)
    fmt.Println(efficient.FindString(text))
}

使用字符类 ​

go
package main

import "regexp"

func main() {
    // 不推荐:使用 | 连接多个选项
    re1 := regexp.MustCompile(`cat|dog|bird`)

    // 推荐:使用字符类 (如果适用)
    re2 := regexp.MustCompile(`[cd][ao][gt]`)

    // 使用 \d 代替 [0-9]
    re3 := regexp.MustCompile(`\d+`)

    // 使用 \w 代替 [a-zA-Z0-9_]
    re4 := regexp.MustCompile(`\w+`)

    // 使用 \s 代替 [ \t\n\r\f\v]
    re5 := regexp.MustCompile(`\s+`)

    _ = re1
    _ = re2
    _ = re3
    _ = re4
    _ = re5
}

实用工具函数 ​

正则工具库 ​

go
package utils

import (
    "regexp"
)

// 常用正则
var (
    EmailRegex     = regexp.MustCompile(`^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$`)
    PhoneRegex     = regexp.MustCompile(`^1[3-9]\d{9}$`)
    URLRegex       = regexp.MustCompile(`^https?://[^\s]+$`)
    IPv4Regex      = regexp.MustCompile(`^(\d{1,3}\.){3}\d{1,3}$`)
    ChineseRegex   = regexp.MustCompile(`[\x{4e00}-\x{9fa5}]+`)
    AlphanumericRegex = regexp.MustCompile(`^[a-zA-Z0-9]+$`)
)

// ValidateEmail 验证邮箱格式
func ValidateEmail(email string) bool {
    return EmailRegex.MatchString(email)
}

// ValidatePhone 验证手机号
func ValidatePhone(phone string) bool {
    return PhoneRegex.MatchString(phone)
}

// ValidateURL 验证 URL
func ValidateURL(url string) bool {
    return URLRegex.MatchString(url)
}

// HasChinese 是否包含中文
func HasChinese(s string) bool {
    return ChineseRegex.MatchString(s)
}

// RemoveChinese 移除中文字符
func RemoveChinese(s string) string {
    return ChineseRegex.ReplaceAllString(s, "")
}

// ExtractChinese 提取中文字符
func ExtractChinese(s string) []string {
    return ChineseRegex.FindAllString(s, -1)
}

// MaskPhone 手机号脱敏
func MaskPhone(phone string) string {
    return PhoneRegex.ReplaceAllString(phone, "${1:0:3}****${1:7:4}")
}

// MaskEmail 邮箱脱敏
func MaskEmail(email string) string {
    re := regexp.MustCompile(`^(\w)[\w._%+-]*@([\w.-]+\.[a-zA-Z]{2,})$`)
    return re.ReplaceAllString(email, "$1***@$2")
}

// SanitizeFilename 清理文件名 (移除非法字符)
func SanitizeFilename(name string) string {
    re := regexp.MustCompile(`[<>:"/\\|?*]`)
    return re.ReplaceAllString(name, "_")
}

// ExtractHashtags 提取标签
func ExtractHashtags(text string) []string {
    re := regexp.MustCompile(`#\w+`)
    return re.FindAllString(text, -1)
}

// ExtractMentions 提取@提及
func ExtractMentions(text string) []string {
    re := regexp.MustCompile(`@\w+`)
    return re.FindAllString(text, -1)
}

正则检查清单 ​

[ ] 预编译正则表达式 (全局变量)
[ ] 使用原始字符串字面量 `...`
[ ] 优先使用 MatchString 而不是 FindString
[ ] 使用非贪婪匹配避免过度匹配
[ ] 使用字符类代替多个 | 选项
[ ] 避免使用 (.*) 捕获不必要的内容
[ ] 使用 (?:...) 代替 (...) 当不需要捕获时
[ ] 对大文本使用 FindAll 限制数量
[ ] 验证用户输入时使用 ^ 和 $ 锚点
[ ] 考虑使用 strings.Contains 等简单方法代替正则
[ ] 复杂正则添加注释说明
[ ] 测试边界情况