Golang Regexp.FindStringSubmatchIndex
最后修改于 2025 年 4 月 20 日
本教程将介绍如何在 Go 中使用 `Regexp.FindStringSubmatchIndex` 方法。我们将涵盖其功能并提供实际示例。
一个 正则表达式 是一个定义搜索模式的字符序列。它用于在字符串中进行模式匹配。
`Regexp.FindStringSubmatchIndex` 方法返回一个切片,其中包含识别最左边匹配及其子匹配的索引对。
基本的 FindStringSubmatchIndex 示例
`FindStringSubmatchIndex` 最简单的用法是查找匹配项及其位置。在这里,我们定位一个简单的单词及其位置。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`hello`)
str := "hello world"
indices := re.FindStringSubmatchIndex(str)
fmt.Println(indices) // [0 5]
if indices != nil {
fmt.Println("Match found at:", indices[0], "to", indices[1])
fmt.Println("Matched text:", str[indices[0]:indices[1]])
}
}
该方法返回一个切片,其中 `[0]` 是匹配的起始索引,`[1]` 是结束索引。我们可以使用它们来提取匹配的子字符串。
查找子匹配位置
此示例展示了如何获取带括号的子表达式的位置。我们将解析日期字符串并获取组件的位置。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
str := "Today is 2025-04-20"
indices := re.FindStringSubmatchIndex(str)
if indices != nil {
fmt.Println("Full match:", str[indices[0]:indices[1]])
fmt.Println("Year:", str[indices[2]:indices[3]])
fmt.Println("Month:", str[indices[4]:indices[5]])
fmt.Println("Day:", str[indices[6]:indices[7]])
}
}
索引在起始和结束位置之间交替。完整匹配在 `[0:1]`,第一个子匹配在 `[2:3]`,第二个在 `[4:5]`,依此类推。
提取多个匹配项
此示例演示了如何在字符串中查找所有匹配项及其位置。我们将定位所有以 'h' 开头的单词。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\bh\w+`)
str := "hello world, hi there, how are you?"
allIndices := re.FindAllStringSubmatchIndex(str, -1)
for _, indices := range allIndices {
word := str[indices[0]:indices[1]]
fmt.Printf("Found '%s' at %d-%d\n", word, indices[0], indices[1])
}
}
`FindAllStringSubmatchIndex` 返回一个索引切片。每个内部切片包含一个匹配项的位置。
命名捕获组
此示例展示了如何处理命名捕获组及其位置。我们将把 URL 解析成组件。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(?P<protocol>https?)://(?P<host>[^/]+)(?P<path>/.*)?`)
str := "https://example.com/path/to/resource"
indices := re.FindStringSubmatchIndex(str)
if indices != nil {
for i, name := range re.SubexpNames() {
if i != 0 && name != "" {
start, end := indices[2*i], indices[2*i+1]
fmt.Printf("%s: %s\n", name, str[start:end])
}
}
}
}
命名组可以通过 `SubexpNames` 访问。索引遵循相同的模式,但可以映射到名称以获得清晰度。
验证和提取数据
此示例在提取组件的同时验证字符串格式。我们将检查电话号码格式并获取其部分。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^(\d{3})-(\d{3})-(\d{4})$`)
phone := "123-456-7890"
indices := re.FindStringSubmatchIndex(phone)
if indices != nil {
fmt.Println("Valid phone number")
fmt.Println("Area code:", phone[indices[2]:indices[3]])
fmt.Println("Exchange:", phone[indices[4]:indices[5]])
fmt.Println("Subscriber:", phone[indices[6]:indices[7]])
} else {
fmt.Println("Invalid phone number format")
}
}
该方法具有双重目的:通过非 nil 返回值进行验证,并通过位置索引进行数据提取。
复杂模式匹配
此示例演示了具有嵌套组的更复杂模式。我们将解析带有时间戳和严重性级别的日志条目。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`^\[(?P<time>\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\] (?P<level>\w+): (?P<message>.*)`)
log := "[2025-04-20 14:30:00] ERROR: Database connection failed"
indices := re.FindStringSubmatchIndex(log)
if indices != nil {
names := re.SubexpNames()
for i := 1; i < len(names); i++ {
start, end := indices[2*i], indices[2*i+1]
fmt.Printf("%s: %s\n", names[i], log[start:end])
}
}
}
该模式使用命名组以获得清晰度。索引数组随着模式中每个附加捕获组的增加而增长。
性能注意事项
当性能很重要时,请考虑重用编译的模式和结果。此示例对不同方法进行了基准测试。
package main
import (
"fmt"
"regexp"
"time"
)
func main() {
str := "sample text with multiple 2025-04-20 dates to 2025-04-21 find"
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
// Approach 1: Compile and find in loop
start := time.Now()
for i := 0; i < 1000; i++ {
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
re.FindStringSubmatchIndex(str)
}
fmt.Println("Compile in loop:", time.Since(start))
// Approach 2: Precompile and reuse
start = time.Now()
for i := 0; i < 1000; i++ {
re.FindStringSubmatchIndex(str)
}
fmt.Println("Reuse compiled:", time.Since(start))
}
基准测试显示重用编译的模式可以带来显著的性能提升。尽可能优先选择预编译。
来源
本教程通过实际的模式匹配和位置提取示例,介绍了 Go 中的 `Regexp.FindStringSubmatchIndex` 方法。