ZetCode

Golang Regexp.FindStringSubmatchIndex

最后修改于 2025 年 4 月 20 日

本教程将介绍如何在 Go 中使用 `Regexp.FindStringSubmatchIndex` 方法。我们将涵盖其功能并提供实际示例。

一个 正则表达式 是一个定义搜索模式的字符序列。它用于在字符串中进行模式匹配。

`Regexp.FindStringSubmatchIndex` 方法返回一个切片,其中包含识别最左边匹配及其子匹配的索引对。

基本的 FindStringSubmatchIndex 示例

`FindStringSubmatchIndex` 最简单的用法是查找匹配项及其位置。在这里,我们定位一个简单的单词及其位置。

basic_match.go
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]` 是结束索引。我们可以使用它们来提取匹配的子字符串。

查找子匹配位置

此示例展示了如何获取带括号的子表达式的位置。我们将解析日期字符串并获取组件的位置。

submatch_positions.go
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' 开头的单词。

multiple_matches.go
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 解析成组件。

named_groups.go
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` 访问。索引遵循相同的模式,但可以映射到名称以获得清晰度。

验证和提取数据

此示例在提取组件的同时验证字符串格式。我们将检查电话号码格式并获取其部分。

validation_extraction.go
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 返回值进行验证,并通过位置索引进行数据提取。

复杂模式匹配

此示例演示了具有嵌套组的更复杂模式。我们将解析带有时间戳和严重性级别的日志条目。

complex_pattern.go
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])
        }
    }
}

该模式使用命名组以获得清晰度。索引数组随着模式中每个附加捕获组的增加而增长。

性能注意事项

当性能很重要时,请考虑重用编译的模式和结果。此示例对不同方法进行了基准测试。

performance.go
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 包文档

本教程通过实际的模式匹配和位置提取示例,介绍了 Go 中的 `Regexp.FindStringSubmatchIndex` 方法。

作者

我叫 Jan Bodnar,是一位充满激情的程序员,拥有丰富的编程经验。我从 2007 年开始撰写编程文章。迄今为止,我已撰写了 1400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Go 教程