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 等简单方法代替正则
[ ] 复杂正则添加注释说明
[ ] 测试边界情况