Golang Regexp.FindSubmatch
最后修改于 2025 年 4 月 20 日
本教程将介绍如何在 Go 中使用 Regexp.FindSubmatch 方法。我们将通过实际示例介绍子匹配项的提取。
一个 正则表达式 是一个定义搜索模式的字符序列。它用于在字符串中进行模式匹配。
Regexp.FindSubmatch 方法返回一个字符串切片,其中包含最左边匹配项及其子匹配项的文本。子匹配项是带括号的子表达式的匹配项。
基本 FindSubmatch 示例
FindSubmatch 最简单的用法是提取日期字符串的各个部分。在这里,我们获取年、月和日组件。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
date := "2025-04-20"
matches := re.FindSubmatch([]byte(date))
if matches != nil {
fmt.Println("Full match:", string(matches[0]))
fmt.Println("Year:", string(matches[1]))
fmt.Println("Month:", string(matches[2]))
fmt.Println("Day:", string(matches[3]))
}
}
我们用三个捕获组编译一个模式。FindSubmatch 返回一个切片,其中索引 0 是完整匹配项,后续索引是子匹配项。
提取姓名组件
此示例演示了如何从格式化的字符串中提取名字和姓氏。该模式捕获两个单词组。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\w+)\s+(\w+)`)
name := "John Doe"
matches := re.FindSubmatch([]byte(name))
if len(matches) >= 3 {
fmt.Println("First name:", string(matches[1]))
fmt.Println("Last name:", string(matches[2]))
}
}
模式 (\w+)\s+(\w+) 匹配由空格分隔的两个单词组。我们在索引 1 和 2 处访问子匹配项。
解析 URL 组件
FindSubmatch 可以从 URL 中提取协议、域和路径。此示例展示了一个简单的 URL 解析器。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(https?)://([^/]+)(/.*)?`)
url := "https://example.com/path/to/resource"
matches := re.FindSubmatch([]byte(url))
if matches != nil {
fmt.Println("Protocol:", string(matches[1]))
fmt.Println("Domain:", string(matches[2]))
fmt.Println("Path:", string(matches[3]))
}
}
该模式捕获协议、域和可选路径。第三个组是可选的,因此它可能为空。
提取多个电子邮件地址
此示例在字符串中查找所有电子邮件地址并提取其组件。我们使用带有 FindAllSubmatch 的循环。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`([a-zA-Z0-9._%+-]+)@([a-zA-Z0-9.-]+)\.([a-zA-Z]{2,})`)
text := "Contact us at info@example.com or support@company.co.uk"
allMatches := re.FindAllSubmatch([]byte(text), -1)
for _, match := range allMatches {
fmt.Println("\nFull email:", string(match[0]))
fmt.Println("Username:", string(match[1]))
fmt.Println("Domain:", string(match[2]))
fmt.Println("TLD:", string(match[3]))
}
}
该模式捕获用户名、域和 TLD 组件。FindAllSubmatch 返回输入字符串中的所有匹配项。
处理可选子匹配项
此示例展示了如何处理带有可选组件的模式。我们解析带有可选错误代码的日志条目。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\[(.*?)\]\s(.*?)(?:\s\((\d+)\))?$`)
logs := []string{
"[ERROR] Connection failed",
"[WARNING] Low memory (1024)",
}
for _, log := range logs {
matches := re.FindSubmatch([]byte(log))
if matches != nil {
fmt.Println("\nLevel:", string(matches[1]))
fmt.Println("Message:", string(matches[2]))
if len(matches[3]) > 0 {
fmt.Println("Code:", string(matches[3]))
}
}
}
}
该模式使用 (?:...)? 使错误代码成为可选的。在使用 matches[3] 之前,我们检查其长度。
提取嵌套子匹配项
这个高级示例演示了嵌套捕获组。我们解析带有键值对的配置文件行。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\w+)=((?:"([^"]*)"|([^,]*))(?:,|$))`)
config := `name="John Doe",age=30,city=New York`
allMatches := re.FindAllSubmatch([]byte(config), -1)
for _, match := range allMatches {
fmt.Println("\nKey:", string(match[1]))
if len(match[3]) > 0 {
fmt.Println("Value (quoted):", string(match[3]))
} else {
fmt.Println("Value:", string(match[4]))
}
}
}
该模式同时处理带引号和不带引号的值。嵌套组允许我们区分不同的值格式。
性能注意事项
处理大型文本时,请考虑使用 FindSubmatchIndex 以获得更好的性能。此示例比较了这两种方法。
package main
import (
"fmt"
"regexp"
"time"
)
func main() {
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
text := []byte("Date: 2025-04-20, Time: 12:30")
start := time.Now()
matches := re.FindSubmatch(text)
fmt.Println("FindSubmatch:", string(matches[1]), time.Since(start))
start = time.Now()
indices := re.FindSubmatchIndex(text)
year := text[indices[2]:indices[3]]
fmt.Println("FindSubmatchIndex:", string(year), time.Since(start))
}
FindSubmatchIndex 返回字节索引而不是子字符串。这可以避免分配,并且在进行大规模处理时更快。
来源
本教程通过实际的字符串子匹配项提取示例,介绍了 Go 中的 Regexp.FindSubmatch 方法。