最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Go字符串查找的20種實(shí)現(xiàn)方式

 更新時(shí)間:2026年05月17日 14:09:57   作者:刀法如飛  
本文總結(jié)了Go語言中20種字符串查找方法,從標(biāo)準(zhǔn)庫API到經(jīng)典高效算法,再到數(shù)據(jù)結(jié)構(gòu)輔助和高級(jí)技巧,覆蓋了從單次查找、多個(gè)模式查找、前綴查詢到模糊匹配等場(chǎng)景,每種方法都有其適用場(chǎng)景和時(shí)間空間復(fù)雜度,并提供了詳細(xì)的應(yīng)用建議,需要的朋友可以參考下

字符串查找(在主串中找模式串第一次或全部出現(xiàn)的位置)是最常見的算法??此浦灰恍?strings.Index,但背后有幾十年的算法演進(jìn)——同一個(gè)任務(wù),樸素算法 O(m×n),KMP 是 O(m+n),Boyer-Moore 在自然文本上接近 O(n/m),Bitap 把位并行做到極致。本文整理 Go 字符串查找的 20 種寫法,按 5 個(gè)策略分類。

為什么有這么多算法?

最簡(jiǎn)單的寫法,把模式串與主串的每個(gè)位置對(duì)齊,逐字節(jié)比較:

func find(text, pattern string) int {
    n, m := len(text), len(pattern)
    for i := 0; i <= n-m; i++ {
        j := 0
        for j < m && text[i+j] == pattern[j] {
            j++
        }
        if j == m {
            return i
        }
    }
    return -1
}

問題在于"匹配失敗時(shí)把所有已匹配的信息都丟了"——回到 i+1 重頭比,復(fù)雜度退化成 O(m×n)。

優(yōu)化思路:讓"匹配失敗"也帶來信息

  • 預(yù)處理模式串:KMP 算 next 數(shù)組、BM 算壞字符表、Sunday 算下一字符位置
  • 滑動(dòng)得更遠(yuǎn):BM/Sunday 一次跳很多位,對(duì)長(zhǎng)模式串極快
  • 哈希指紋:Rabin-Karp 用滾動(dòng)哈希把"逐字符比較"壓成 O(1)
  • 位并行:Bitap 用 uint64 表示"模式的所有前綴是否匹配",一次 CPU 指令推進(jìn)多位
  • 多模式合并:AC 自動(dòng)機(jī)把 N 個(gè)模式串合成一個(gè) Trie,掃一遍主串找出所有
  • 數(shù)據(jù)結(jié)構(gòu):Trie 用于前綴查詢、后綴數(shù)組用于多次查詢同一文本

Go 的特殊點(diǎn)

  • string 是只讀的 []byte,按字節(jié) O(1) 索引;但 UTF-8 解碼需要 for ... rangerune
  • 標(biāo)準(zhǔn)庫 strings 是工業(yè)級(jí)實(shí)現(xiàn)(含 Rabin-Karp 大模式優(yōu)化)
  • bytes 包對(duì) []byte 提供同樣 API,處理二進(jìn)制流不必經(jīng) string 轉(zhuǎn)換

推薦方案

需求代碼性能
單次查找strings.Index(text, pat)標(biāo)準(zhǔn)庫實(shí)測(cè)最快
判斷是否存在strings.Contains(text, pat)內(nèi)部就是 Index ≥ 0
復(fù)雜模式regexp.MustCompile(...).FindIndex內(nèi)部是 RE2,O(n)
字節(jié)流bytes.Index(buf, pat)避免 string 拷貝
多模式同時(shí)查AC 自動(dòng)機(jī)O(n + 輸出)
模糊匹配BitapO(n × k)

第1類:標(biāo)準(zhǔn)庫 API(方法1-5)

策略原理:Go 的 strings 包對(duì)短模式用樸素 + 字節(jié)掃描優(yōu)化,長(zhǎng)模式用 Rabin-Karp。regexp 用 RE2 引擎,保證線性時(shí)間,無 ReDoS 風(fēng)險(xiǎn)。生產(chǎn)代碼默認(rèn)應(yīng)該先用這些。

// 方法1:strings.Index —— 標(biāo)準(zhǔn)庫最常用
// 短模式走樸素,長(zhǎng)模式自動(dòng)切到 Rabin-Karp(見 Go 源碼 strings/search.go)
func find1(text, pattern string) int {
    return strings.Index(text, pattern)
}

// 方法2:strings.Contains —— 只關(guān)心"是否存在"
// 等價(jià)于 Index(...) >= 0,語義更明確
func find2(text, pattern string) bool {
    return strings.Contains(text, pattern)
}

// 方法3:HasPrefix 滑動(dòng)窗口
// 不構(gòu)造子串、零分配
func find3(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    for i := 0; i <= n-m; i++ {
        if strings.HasPrefix(text[i:], pattern) {
            return i
        }
    }
    return -1
}

// 方法4:regexp 正則——支持復(fù)雜模式、大小寫不敏感
// regexp.QuoteMeta 把字符串里的元字符轉(zhuǎn)義,防止把用戶輸入當(dāng)正則解析
func find4(text, pattern string) []int {
    re := regexp.MustCompile(regexp.QuoteMeta(pattern))
    locs := re.FindAllStringIndex(text, -1)
    result := make([]int, 0, len(locs))
    for _, loc := range locs {
        result = append(result, loc[0])
    }
    return result
}

// 方法5:bytes.Index —— []byte 流上的查找
// 處理網(wǎng)絡(luò)包、文件讀取緩沖區(qū)時(shí),比 string(buf) 轉(zhuǎn)換零拷貝
func find5(buf, pattern []byte) int {
    return bytes.Index(buf, pattern)
}

小心兩個(gè)坑:① Go 的 string 索引是字節(jié)而非 rune,對(duì)中文/emoji 直接 text[i] 拿到的是 UTF-8 的某個(gè)字節(jié),不是字符;② 不可信用戶輸入做正則前必須 regexp.QuoteMeta 轉(zhuǎn)義,否則一個(gè) .* 就會(huì)引發(fā)性能問題(雖然 RE2 沒有 ReDoS,但仍可能慢)。

第2類:樸素與暴力(方法6-9)

策略原理:不依賴任何預(yù)處理,純靠下標(biāo)掃描。每個(gè)位置都重新比較 O(m) 次,最壞復(fù)雜度 O(m×n)。這是所有高級(jí)算法的起點(diǎn)——理解樸素算法的"浪費(fèi)在哪里",才能理解 KMP/BM 的優(yōu)化點(diǎn)。

// 方法6:雙循環(huán)樸素——最經(jīng)典的 Brute Force
// 已是 nativesearch/string_search.go 中的實(shí)現(xiàn)
func find6(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    for i := 0; i <= n-m; i++ {
        j := 0
        for j < m && text[i+j] == pattern[j] {
            j++
        }
        if j == m {
            return i
        }
    }
    return -1
}

// 方法7:[]byte 數(shù)組版——對(duì) string 直接做字節(jié)索引就是這種效果
// Go 的 string 已經(jīng)是只讀 byte 數(shù)組,[]byte(text) 會(huì)觸發(fā)拷貝
// 對(duì) string 直接 text[i] 沒有邊界檢查開銷,與方法6性能一致
func find7(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    for i := 0; i <= n-m; i++ {
        match := true
        for j := 0; j < m; j++ {
            if text[i+j] != pattern[j] {
                match = false
                break
            }
        }
        if match {
            return i
        }
    }
    return -1
}

// 方法8:rune 版本——按 Unicode 字符比較
// 處理中文、emoji 時(shí)必須用 rune 而非 byte
// 注意:rune 切片會(huì)消耗 O(n) 額外空間
func find8(text, pattern string) int {
    tr, pr := []rune(text), []rune(pattern)
    n, m := len(tr), len(pr)
    if m == 0 {
        return 0
    }
    for i := 0; i <= n-m; i++ {
        j := 0
        for j < m && tr[i+j] == pr[j] {
            j++
        }
        if j == m {
            // 注意:返回的是 rune 下標(biāo),要算字節(jié)位置需要遍歷前綴
            return i
        }
    }
    return -1
}

// 方法9:反向樸素——從右往左對(duì)齊
// BM 算法的雛形:從模式串最右開始比,失敗時(shí)直接跳到下一對(duì)齊
func find9(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    for i := n - m; i >= 0; i-- {
        j := m - 1
        for j >= 0 && text[i+j] == pattern[j] {
            j--
        }
        if j < 0 {
            return i
        }
    }
    return -1
}

樸素算法的最壞案例text = "AAAAA...AAB"、pattern = "AAAB"——前 m-1 個(gè)字符總是匹配,最后一個(gè)總是失敗。每對(duì)齊一次浪費(fèi) O(m),總浪費(fèi) O(m×n)。

第3類:經(jīng)典高效算法(方法10-14)

策略原理:通過對(duì)模式串的預(yù)處理,讓"失敗時(shí)"不再從頭開始。代價(jià)是 O(m) 或 O(σ) 的預(yù)處理空間。這五種是字符串匹配的"教科書算法"。

// 方法10:KMP 算法——利用已匹配信息避免回溯
// 完整實(shí)現(xiàn)見 KMPsearch/kmp_search.go
// 核心:next[i] = pattern[0..i] 的最長(zhǎng)真前綴也是真后綴的長(zhǎng)度
func find10(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    // 構(gòu)建 next 數(shù)組
    next := make([]int, m)
    k := 0
    for i := 1; i < m; i++ {
        for k > 0 && pattern[i] != pattern[k] {
            k = next[k-1]
        }
        if pattern[i] == pattern[k] {
            k++
        }
        next[i] = k
    }
    // 主串掃描,j 永不回退
    j := 0
    for i := 0; i < n; i++ {
        for j > 0 && text[i] != pattern[j] {
            j = next[j-1]
        }
        if text[i] == pattern[j] {
            j++
        }
        if j == m {
            return i - m + 1
        }
    }
    return -1
}

// 方法11:Boyer-Moore(壞字符規(guī)則)
// 從模式串右端開始比,失配按"該字符在模式中最右出現(xiàn)位置"跳躍
// 在自然文本中常常能跳過 m 位(接近線性時(shí)間)
func find11(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    // 壞字符表:256 字節(jié)字符集
    var badChar [256]int
    for i := range badChar {
        badChar[i] = -1
    }
    for i := 0; i < m; i++ {
        badChar[pattern[i]] = i
    }
    shift := 0
    for shift <= n-m {
        j := m - 1
        for j >= 0 && pattern[j] == text[shift+j] {
            j--
        }
        if j < 0 {
            return shift
        }
        // max(1, ...) 防止跳到負(fù)數(shù)
        delta := j - badChar[text[shift+j]]
        if delta < 1 {
            delta = 1
        }
        shift += delta
    }
    return -1
}

// 方法12:Sunday 算法——BM 的簡(jiǎn)化變種
// 關(guān)鍵:失配時(shí)看"窗口右側(cè)外那一格",它將來必然要參與對(duì)齊
// 在英文文本上常常比 BM 還快
func find12(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    // shift[c] = 字符 c 在模式串中最右出現(xiàn)位置,讓其與新窗口對(duì)齊
    var shift [256]int
    for i := range shift {
        shift[i] = m + 1
    }
    for i := 0; i < m; i++ {
        shift[pattern[i]] = m - i
    }
    i := 0
    for i <= n-m {
        j := 0
        for j < m && text[i+j] == pattern[j] {
            j++
        }
        if j == m {
            return i
        }
        if i+m >= n {
            return -1
        }
        i += shift[text[i+m]]
    }
    return -1
}

// 方法13:Horspool 算法——BM 的另一種簡(jiǎn)化
// 失配時(shí)只看"主串對(duì)齊到模式串末尾的字符",不計(jì)算 j-badChar[c]
// 代碼更短,性能在多數(shù)場(chǎng)景接近 BM
func find13(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    var shift [256]int
    for i := range shift {
        shift[i] = m
    }
    for i := 0; i < m-1; i++ {
        shift[pattern[i]] = m - 1 - i
    }
    i := 0
    for i <= n-m {
        j := m - 1
        for j >= 0 && pattern[j] == text[i+j] {
            j--
        }
        if j < 0 {
            return i
        }
        i += shift[text[i+m-1]]
    }
    return -1
}

// 方法14:Rabin-Karp(滾動(dòng)哈希)
// 完整實(shí)現(xiàn)見 pattern-matching/ 中類比版本
// 核心:先比較窗口的哈希,相等再確認(rèn)(解決沖突)
// 多模式同時(shí)查找時(shí)極其有用——所有模式串預(yù)算哈希存 map,掃一遍主串
func find14(text, pattern string) int {
    n, m := len(text), len(pattern)
    if m == 0 {
        return 0
    }
    if m > n {
        return -1
    }
    const (
        D = 256
        Q = 1000000007 // 大素?cái)?shù)防沖突
    )
    h := uint64(1)
    for i := 0; i < m-1; i++ {
        h = (h * D) % Q
    }
    var p, t uint64
    for i := 0; i < m; i++ {
        p = (D*p + uint64(pattern[i])) % Q
        t = (D*t + uint64(text[i])) % Q
    }
    for i := 0; i <= n-m; i++ {
        if p == t {
            j := 0
            for j < m && text[i+j] == pattern[j] {
                j++
            }
            if j == m {
                return i
            }
        }
        if i < n-m {
            // Go 中 uint 減法是模運(yùn)算,不會(huì)成負(fù),但 (t - text[i]*h) 可能溢出
            // 用 +Q 保正再取模
            t = (D*((t+Q-uint64(text[i])*h%Q)%Q) + uint64(text[i+m])) % Q
        }
    }
    return -1
}

第4類:數(shù)據(jù)結(jié)構(gòu)輔助(方法15-17)

策略原理:當(dāng)"一次查找"變成"反復(fù)多次"時(shí),把成本前置——預(yù)處理一次,之后查詢接近 O(m) 或 O(log n)。

// 方法15:Trie 前綴樹——多個(gè)模式串的前綴查詢
// 適合自動(dòng)補(bǔ)全、敏感詞字典查詢等場(chǎng)景
type Trie struct {
    children [26]*Trie // 僅小寫英文,工程里用 map[byte]*Trie
    isEnd    bool
}

func (t *Trie) Insert(word string) {
    cur := t
    for i := 0; i < len(word); i++ {
        idx := word[i] - 'a'
        if cur.children[idx] == nil {
            cur.children[idx] = &Trie{}
        }
        cur = cur.children[idx]
    }
    cur.isEnd = true
}

func (t *Trie) Contains(word string) bool {
    n := t.walk(word)
    return n != nil && n.isEnd
}

func (t *Trie) StartsWith(prefix string) bool {
    return t.walk(prefix) != nil
}

func (t *Trie) walk(s string) *Trie {
    cur := t
    for i := 0; i < len(s); i++ {
        cur = cur.children[s[i]-'a']
        if cur == nil {
            return nil
        }
    }
    return cur
}

// 方法16:AC 自動(dòng)機(jī)——Trie + 失敗指針
// 一次掃描主串,找出所有模式串的所有出現(xiàn)
// 是敏感詞過濾、入侵檢測(cè)的標(biāo)準(zhǔn)算法
type ACNode struct {
    children map[byte]*ACNode
    fail     *ACNode
    hits     []string // 該節(jié)點(diǎn)結(jié)尾的模式串
}

type AhoCorasick struct {
    root *ACNode
}

func NewAhoCorasick() *AhoCorasick {
    return &AhoCorasick{root: &ACNode{children: map[byte]*ACNode{}}}
}

func (ac *AhoCorasick) Add(p string) {
    cur := ac.root
    for i := 0; i < len(p); i++ {
        c := p[i]
        if cur.children[c] == nil {
            cur.children[c] = &ACNode{children: map[byte]*ACNode{}}
        }
        cur = cur.children[c]
    }
    cur.hits = append(cur.hits, p)
}

func (ac *AhoCorasick) Build() {
    queue := []*ACNode{}
    for _, child := range ac.root.children {
        child.fail = ac.root
        queue = append(queue, child)
    }
    for len(queue) > 0 {
        u := queue[0]
        queue = queue[1:]
        for c, v := range u.children {
            // v 的失敗指針:從 u.fail 沿 c 走
            f := u.fail
            for f != nil && f.children[c] == nil {
                f = f.fail
            }
            if f == nil {
                v.fail = ac.root
            } else {
                v.fail = f.children[c]
            }
            // 累加失敗鏈上的命中
            v.hits = append(v.hits, v.fail.hits...)
            queue = append(queue, v)
        }
    }
}

func (ac *AhoCorasick) Search(text string) [][2]int {
    var result [][2]int
    cur := ac.root
    for i := 0; i < len(text); i++ {
        c := text[i]
        for cur != ac.root && cur.children[c] == nil {
            cur = cur.fail
        }
        if next := cur.children[c]; next != nil {
            cur = next
        }
        for _, hit := range cur.hits {
            result = append(result, [2]int{i - len(hit) + 1, len(hit)})
        }
    }
    return result
}

// 方法17:后綴數(shù)組 + 二分查找
// 適合"主串固定、反復(fù)查不同模式"的場(chǎng)景
// 樸素構(gòu)建 O(n2 log n),sort 接口帶來便利
type SuffixArray struct {
    text string
    sa   []int
}

func NewSuffixArray(text string) *SuffixArray {
    n := len(text)
    sa := make([]int, n)
    for i := range sa {
        sa[i] = i
    }
    // 樸素 O(n2 log n);工程上用 SA-IS 實(shí)現(xiàn) O(n)
    sort.Slice(sa, func(i, j int) bool {
        return text[sa[i]:] < text[sa[j]:]
    })
    return &SuffixArray{text: text, sa: sa}
}

func (s *SuffixArray) Search(pattern string) int {
    lo, hi := 0, len(s.sa)-1
    for lo <= hi {
        mid := (lo + hi) / 2
        suf := s.text[s.sa[mid]:]
        if strings.HasPrefix(suf, pattern) {
            return s.sa[mid]
        }
        if suf < pattern {
            lo = mid + 1
        } else {
            hi = mid - 1
        }
    }
    return -1
}

Go 標(biāo)準(zhǔn)庫 index/suffixarray 已提供 SuffixArray,O(n log n) 構(gòu)建,Lookup(pat, n) 直接給出全部匹配位置。生產(chǎn)代碼用它就夠了。

第5類:高級(jí)技巧(方法18-20)

// 方法18:strings.Reader + 流式查找
// 不能一次讀進(jìn)內(nèi)存的大文件可以這么處理
func find18(reader *strings.Reader, pattern string) []int {
    // 簡(jiǎn)化:先全部讀出來。真實(shí)場(chǎng)景用 bufio.Reader 分塊 + 上下文保留
    data, _ := io.ReadAll(reader)
    var result []int
    text := string(data)
    for i := 0; ; {
        pos := strings.Index(text[i:], pattern)
        if pos < 0 {
            break
        }
        result = append(result, i+pos)
        i += pos + 1
    }
    return result
}

// 方法19:Z 算法——線性時(shí)間擴(kuò)展前綴
// Z[i] = 以 i 開頭的后綴與原串的最長(zhǎng)公共前綴長(zhǎng)度
// 拼接 "pattern + 分隔符 + text" 跑一遍 Z,所有 Z[i]==m 的位置就是匹配
func find19(text, pattern string) int {
    m := len(pattern)
    if m == 0 {
        return 0
    }
    s := pattern + "#" + text
    z := computeZ(s)
    for i := m + 1; i < len(s); i++ {
        if z[i] == m {
            return i - m - 1
        }
    }
    return -1
}

func computeZ(s string) []int {
    n := len(s)
    z := make([]int, n)
    l, r := 0, 0
    for i := 1; i < n; i++ {
        if i < r {
            if z[i-l] < r-i {
                z[i] = z[i-l]
            } else {
                z[i] = r - i
            }
        }
        for i+z[i] < n && s[z[i]] == s[i+z[i]] {
            z[i]++
        }
        if i+z[i] > r {
            l, r = i, i+z[i]
        }
    }
    return z
}

// 方法20:Bitap (Shift-And) ——位并行匹配
// 用 uint64 的每一位表示"模式串前綴 i 是否匹配到當(dāng)前位置"
// 一次位運(yùn)算推進(jìn)所有前綴,CPU 上極快
// 限制:模式串長(zhǎng)度 ≤ 64
// 真正威力:模糊匹配——k 個(gè) uint64 數(shù)組同時(shí)跟蹤 k 個(gè)錯(cuò)誤內(nèi)的所有匹配
func find20(text, pattern string) int {
    m := len(pattern)
    if m == 0 {
        return 0
    }
    if m > 63 {
        panic("Bitap 單 uint64 版只支持 m <= 63")
    }
    var mask [256]uint64
    for i := 0; i < m; i++ {
        mask[pattern[i]] |= 1 << i
    }
    var state uint64
    matchBit := uint64(1) << (m - 1)
    for i := 0; i < len(text); i++ {
        // 推進(jìn)所有前綴進(jìn)度,然后與當(dāng)前字符的 mask 相與
        state = ((state << 1) | 1) & mask[text[i]]
        if state&matchBit != 0 {
            return i - m + 1
        }
    }
    return -1
}

選擇指南

類別時(shí)間復(fù)雜度空間主要場(chǎng)景
標(biāo)準(zhǔn)庫 APIO(m+n) 實(shí)測(cè)O(1)日常 95% 場(chǎng)景
樸素與暴力O(m×n)O(1)教學(xué)、面試、極短模式
經(jīng)典算法O(m+n) ~ O(n/m)O(m+σ)單次查找的標(biāo)準(zhǔn)方案
數(shù)據(jù)結(jié)構(gòu)預(yù)處理 O(n),查詢 O(m)O(總規(guī)模)海量查詢
高級(jí)技巧O(n)O(m) ~ O(σ)模糊匹配 / 流式

實(shí)際項(xiàng)目里怎么選

絕大多數(shù)情況一行就夠:

// 單次查找:標(biāo)準(zhǔn)庫已經(jīng)夠好
pos := strings.Index(text, pattern)

// 找全部位置
re := regexp.MustCompile(regexp.QuoteMeta(pattern))
locs := re.FindAllStringIndex(text, -1) // [[start1,end1], [start2,end2], ...]

模式串很長(zhǎng)(≥ 20)且文本是自然語言:

// 標(biāo)準(zhǔn)庫已經(jīng)在長(zhǎng)模式上自動(dòng)切到 Rabin-Karp
// 一般不需要自己實(shí)現(xiàn) BM/Sunday
pos := strings.Index(text, longPattern)

需要在同一文本上反復(fù)查多個(gè)模式:

// AC 自動(dòng)機(jī)
ac := NewAhoCorasick()
for _, p := range patterns {
    ac.Add(p)
}
ac.Build()
hits := ac.Search(text)

需要在同一文本上做大量不相關(guān)查詢:

// 標(biāo)準(zhǔn)庫后綴數(shù)組
import "index/suffixarray"
sa := suffixarray.New([]byte(largeText))
positions := sa.Lookup([]byte(pattern), -1) // 返回全部出現(xiàn)位置

需要前綴查詢、自動(dòng)補(bǔ)全:

trie := &Trie{}
for _, w := range dictionary {
    trie.Insert(w)
}
exists := trie.Contains("apple")
prefix := trie.StartsWith("app")

多模式匹配的處理

不要循環(huán)調(diào)用 strings.Index:

// ? 反例:N 個(gè)模式 × M 次掃描 = O(N × M × n)
for _, p := range patterns {
    if strings.Contains(text, p) {
        hit(p)
    }
}

正確做法(按規(guī)模選擇):

模式數(shù) N推薦方案
N ≤ 5,模式短直接循環(huán) strings.Index
N ≤ 100多模式 Rabin-Karp + map[uint64]string
N 上千AC 自動(dòng)機(jī)
海量動(dòng)態(tài)增刪AC 自動(dòng)機(jī) + 失敗鏈懶更新

正則的 alternation:

// 把多個(gè)模式拼成一個(gè) RE2 正則
// RE2 內(nèi)部會(huì)編譯為 NFA/DFA,效率取決于實(shí)現(xiàn)
parts := make([]string, len(patterns))
for i, p := range patterns {
    parts[i] = regexp.QuoteMeta(p)
}
re := regexp.MustCompile(strings.Join(parts, "|"))
locs := re.FindAllStringIndex(text, -1)

大文本與流式查找

文本不能一次性讀進(jìn)內(nèi)存(GB 級(jí)日志、網(wǎng)絡(luò)流)時(shí):

// 關(guān)鍵:跨緩沖區(qū)邊界的匹配會(huì)被切斷,需要保留 m-1 字符的"上下文"
func streamSearch(r io.Reader, pattern string) []int {
    m := len(pattern)
    buf := make([]byte, 0, 8192+m)
    chunk := make([]byte, 8192)
    var result []int
    var totalOffset int

    for {
        n, err := r.Read(chunk)
        if n > 0 {
            buf = append(buf, chunk[:n]...)
            // 在 buf 里找匹配
            offset := 0
            for {
                idx := bytes.Index(buf[offset:], []byte(pattern))
                if idx < 0 {
                    break
                }
                result = append(result, totalOffset+offset+idx)
                offset += idx + 1
            }
            // 保留末尾 m-1 字節(jié),避免跨緩沖區(qū)漏匹配
            if len(buf) > m-1 {
                drop := len(buf) - (m - 1)
                totalOffset += drop
                buf = buf[drop:]
            }
        }
        if err != nil {
            break
        }
    }
    return result
}

工程里更常見的方式是用 bufio.Scanner 逐行掃,或者直接 mmap 大文件。

字節(jié)、rune 與 Unicode

Go 的 string 是只讀 byte 序列,按字節(jié)索引:

text := "你好world"
fmt.Println(len(text))   // 11("你"、"好"各 3 字節(jié),"world" 5 字節(jié))
fmt.Println(text[0])     // 228(第一個(gè)字節(jié),不是字符 '你')

按字符(rune)處理:

// 方式1:轉(zhuǎn) []rune
runes := []rune(text)
fmt.Println(len(runes))  // 7

// 方式2:for ... range
for i, r := range text {
    fmt.Printf("byte %d: %c\n", i, r)
}

// 方式3:utf8 包
import "unicode/utf8"
fmt.Println(utf8.RuneCountInString(text)) // 7

大小寫不敏感查找:

// 簡(jiǎn)單粗暴:全部轉(zhuǎn)小寫
strings.Contains(strings.ToLower(text), strings.ToLower(pattern))

// 嚴(yán)格 Unicode 折疊(處理土耳其 i/I、德語 ? 等)
strings.EqualFold(s1, s2) // 僅適用于"全等"判斷

// 正則
re := regexp.MustCompile("(?i)" + regexp.QuoteMeta(pattern))

自定義對(duì)象:在切片中查找子序列

strings.Index 只查 string 子串。在 []T 中查 []T 模式,思路完全一樣:

// 在 []T 中查找 []T 模式(T 必須可比較)
func IndexSeq[T comparable](haystack, needle []T) int {
    n, m := len(haystack), len(needle)
    if m == 0 {
        return 0
    }
    for i := 0; i <= n-m; i++ {
        j := 0
        for j < m && haystack[i+j] == needle[j] {
            j++
        }
        if j == m {
            return i
        }
    }
    return -1
}

KMP/BM 等算法都可以泛化為 [T comparable]。如果元素是不可比較的(含 slice、map 字段的 struct),需要自己提取一個(gè)可比較"鍵"再查。

總結(jié)

工程上的快捷選擇:

  • 默認(rèn)用 strings.Index(text, pattern):標(biāo)準(zhǔn)庫已經(jīng)優(yōu)化得很好
  • 找全部位置用 regexp.MustCompile(QuoteMeta(p)).FindAllStringIndex
  • 字節(jié)流上用 bytes.Index,零拷貝
  • 多個(gè)模式同時(shí)查,AC 自動(dòng)機(jī)
  • 同一主串反復(fù)查不同模式,標(biāo)準(zhǔn)庫 index/suffixarray
  • 前綴查詢、自動(dòng)補(bǔ)全,Trie
  • 模糊匹配,Bitap 或 levenshtein
  • Unicode 處理用 unicode/utf8 包,不要直接對(duì) string 做字節(jié)索引

核心思路:

  1. 同一個(gè)問題可以從多個(gè)角度切入——樸素到 KMP 是"利用失敗信息",KMP 到 BM 是"換方向比較",BM 到 Bitap 是"換數(shù)據(jù)表示"
  2. 選對(duì)算法往往比寫更聰明的代碼更重要——AC 自動(dòng)機(jī)一次掃描勝過 N 次 strings.Index
  3. O(m×n) 與 O(m+n) 在數(shù)據(jù)變大時(shí)是幾百倍的實(shí)際差距,但常數(shù)也很重要——KMP 不一定比 strings.Index 快
  4. 不要過度優(yōu)化——能用 strings.Index 就別繞彎
  5. Go 的標(biāo)準(zhǔn)庫已經(jīng)覆蓋了 80% 的場(chǎng)景,理解算法是為了選對(duì)工具,不是替代標(biāo)準(zhǔn)庫

20 種實(shí)現(xiàn)的本質(zhì)是 4 個(gè)升維

  • 把"匹配失敗"變成信息(樸素 → KMP)
  • 把"逐字符比較"變成"批量跳躍"(KMP → BM/Sunday)
  • 把"字符比較"變成"哈希/位運(yùn)算"(BM → Rabin-Karp/Bitap)
  • 把"一次查詢"變成"多次查詢"(→ Trie/AC/SuffixArray)

理解這 4 個(gè)升維方向,寫出第 21、第 22 種都不在話下。

以上就是Go字符串查找的20種實(shí)現(xiàn)方式的詳細(xì)內(nèi)容,更多關(guān)于Go字符串查找的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 使用golang實(shí)現(xiàn)PDF圖片提取

    使用golang實(shí)現(xiàn)PDF圖片提取

    這篇文章主要為大家詳細(xì)介紹了如何使用golang實(shí)現(xiàn)PDF圖片提取功能,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2025-11-11
  • 搭建Go語言的ORM框架Gorm的具體步驟(從Java到go)

    搭建Go語言的ORM框架Gorm的具體步驟(從Java到go)

    很多朋友不知道如何使用Goland軟件,搭建一個(gè)ORM框架GORM,今天小編給大家分享一篇教程關(guān)于搭建Go語言的ORM框架Gorm的具體步驟(從Java到go),感興趣的朋友跟隨小編一起學(xué)習(xí)下吧
    2022-09-09
  • 詳解Go 創(chuàng)建命令行工具的方法

    詳解Go 創(chuàng)建命令行工具的方法

    這篇文章主要介紹了詳解Go 創(chuàng)建命令行工具,需要的朋友可以參考下
    2020-12-12
  • Go語言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用

    Go語言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用

    設(shè)計(jì)模式是軟件工程中各種常見問題的經(jīng)典解決方案,,本文主要介紹了Go語言實(shí)現(xiàn)23種設(shè)計(jì)模式的使用,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-05-05
  • golang 中獲取字符串個(gè)數(shù)的方法

    golang 中獲取字符串個(gè)數(shù)的方法

    這篇文章主要介紹了golang 中獲取字符串個(gè)數(shù) ,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 詳解如何使用unsafe標(biāo)準(zhǔn)庫突破Golang中的類型限制

    詳解如何使用unsafe標(biāo)準(zhǔn)庫突破Golang中的類型限制

    在使用c語言編程時(shí),常常因?yàn)轭愋偷膯栴}大傷腦筋,而,golang提供了一些方式用于喜歡hack的用戶,下面我們就來講講如何使用unsafe標(biāo)準(zhǔn)庫突破Golang中的類型限制吧
    2024-03-03
  • Golang中runtime的使用詳解

    Golang中runtime的使用詳解

    這篇文章主要介紹了Golang中runtime的使用詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-08-08
  • vscode如何debug調(diào)試golang代碼

    vscode如何debug調(diào)試golang代碼

    古話說工欲善其事必先利其器,Go語言程序的開發(fā)者而言,當(dāng)下最火的IDE應(yīng)該非微軟的Visual Studio Code莫屬,本文主要介紹了vscode如何debug調(diào)試golang代碼,感興趣的可以了解一下
    2024-03-03
  • go語言定義零值可用的類型學(xué)習(xí)教程

    go語言定義零值可用的類型學(xué)習(xí)教程

    這篇文章主要為大家介紹了go語言定義零值可用的類型教程學(xué)習(xí),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-06-06
  • GO語言中創(chuàng)建切片的三種實(shí)現(xiàn)方式

    GO語言中創(chuàng)建切片的三種實(shí)現(xiàn)方式

    這篇文章主要介紹了GO語言中創(chuàng)建切片的三種實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-09-09

最新評(píng)論

成安县| 兴宁市| 望谟县| 龙井市| 娄底市| 鸡泽县| 榆树市| 鄂伦春自治旗| 永州市| 镇雄县| 志丹县| 贵州省| 永州市| 平遥县| 城步| 泾川县| 景泰县| 玛多县| 鹤山市| 灌阳县| 武川县| 梁河县| 梁河县| 印江| 灵璧县| 昌吉市| 山西省| 泸溪县| 申扎县| 沙河市| 浏阳市| 芦溪县| 沙田区| 平度市| 黄平县| 德惠市| 新沂市| 张家川| 区。| 广州市| 宾阳县|