正則表達(dá)式詳解以及Golang中的應(yīng)用示例
引言:正則表達(dá)式的價(jià)值與應(yīng)用場(chǎng)景
在現(xiàn)代軟件開(kāi)發(fā)中,文本處理是一項(xiàng)核心任務(wù),而正則表達(dá)式(Regular Expression,簡(jiǎn)稱(chēng) Regex)則是處理文本的瑞士軍刀。其通過(guò)一種簡(jiǎn)潔的語(yǔ)法定義字符串的匹配模式,能夠高效地完成復(fù)雜的文本檢索、驗(yàn)證、替換和提取操作。
正則表達(dá)式的應(yīng)用場(chǎng)景幾乎涵蓋了所有需要處理文本的領(lǐng)域:
- 數(shù)據(jù)驗(yàn)證:檢查用戶(hù)輸入的郵箱、手機(jī)號(hào)、身份證號(hào)等是否符合格式要求
- 日志分析:從海量日志中提取關(guān)鍵信息,如 IP 地址、錯(cuò)誤代碼
- 文本處理:批量替換文檔中的特定內(nèi)容,格式化文本
- 數(shù)據(jù)提取:從 HTML、JSON 等非結(jié)構(gòu)化或半結(jié)構(gòu)化數(shù)據(jù)中提取有用信息
- 代碼生成:根據(jù)特定模式自動(dòng)生成代碼片段
Go語(yǔ)言亦內(nèi)置了正則表達(dá)式支持,在其標(biāo)準(zhǔn)庫(kù)regexp包基于 RE2 引擎實(shí)現(xiàn),保證了線性時(shí)間復(fù)雜度和線程安全性,非常適合在高性能應(yīng)用中使用。
正則表達(dá)式基本概念
正則表達(dá)式是由普通字符和元字符組成的字符串模式,用于描述一類(lèi)字符串的特征。當(dāng)我們使用正則表達(dá)式匹配文本時(shí),實(shí)際上是檢查目標(biāo)文本是否符合這個(gè)模式定義的特征。
普通字符
普通字符是指在正則表達(dá)式中沒(méi)有特殊含義,僅表示其自身的字符。例如:
- 字母:
a-z、A-Z- 數(shù)字:
0-9- 部分符號(hào):
_、-、+等(不包括元字符)
示例:
- 正則表達(dá)式
hello將精確匹配字符串 “hello”- 正則表達(dá)式
123將精確匹配字符串 “123”
元字符
元字符是正則表達(dá)式中具有特殊含義的字符,它們用于構(gòu)建復(fù)雜的匹配模式。掌握元字符的用法是學(xué)習(xí)正則表達(dá)式的關(guān)鍵。
1. 基礎(chǔ)元字符
| 元字符 | 描述 | 示例 |
|---|---|---|
. | 匹配任意單個(gè)字符(除換行符\n外) | a.b匹配 “aab”、“acb”、“a3b” 等 |
^ | 匹配字符串的開(kāi)頭位置 | ^hello匹配以 “hello” 開(kāi)頭的字符串 |
$ | 匹配字符串的結(jié)尾位置 | world$匹配以 “world” 結(jié)尾的字符串 |
\ | 轉(zhuǎn)義字符,使后續(xù)字符失去特殊含義 | \.匹配點(diǎn)號(hào)本身,\*匹配星號(hào)本身 |
示例解析:
^abc$精確匹配字符串 “abc”(從開(kāi)頭到結(jié)尾完全一致)^a.c$匹配 “abc”、“a1c”、“a#c” 等,但不匹配 “ac”、“abdc”hello\.world匹配 “hello.world”,而不是 “helloworld” 或 “helloXworld”
2. 字符類(lèi)(Character Classes)
字符類(lèi)用于定義一組可能匹配的字符,用方括號(hào)[]表示。
| 表達(dá)式 | 描述 |
|---|---|
[abc] | 匹配 a、b 或 c 中的任意一個(gè)字符 |
[a-z] | 匹配任意小寫(xiě)字母 |
[A-Z] | 匹配任意大寫(xiě)字母 |
[0-9] | 匹配任意數(shù)字 |
[a-zA-Z0-9] | 匹配任意字母或數(shù)字 |
[^abc] | 匹配除 a、b、c 之外的任意字符(^ 表示取反) |
[a-dm-p] | 匹配 a-d 或 m-p 范圍內(nèi)的字符 |
示例解析:
[Hh]ello匹配 “Hello” 或 “hello”[0-9]{4}匹配任意 4 位數(shù)字[^0-9]匹配非數(shù)字字符[a-zA-Z_][a-zA-Z0-9_]*匹配符合變量命名規(guī)則的字符串
3. 預(yù)定義字符類(lèi)
為了簡(jiǎn)化常用的字符類(lèi),正則表達(dá)式定義了一系列預(yù)定義字符類(lèi):
| 表達(dá)式 | 等價(jià)形式 | 描述 |
|---|---|---|
\d | [0-9] | 匹配任意數(shù)字字符 |
\D | [^0-9] | 匹配任意非數(shù)字字符 |
\w | [a-zA-Z0-9_] | 匹配字母、數(shù)字或下劃線 |
\W | [^a-zA-Z0-9_] | 匹配非字母、非數(shù)字、非下劃線 |
\s | [ \t\n\r\f\v] | 匹配任意空白字符(空格、制表符、換行符等) |
\S | [^ \t\n\r\f\v] | 匹配任意非空白字符 |
注意:
在 GOLANG的字符串中,反斜杠
\是轉(zhuǎn)義字符,因此在編寫(xiě)正則表達(dá)式時(shí),需要使用兩個(gè)反斜杠\\來(lái)表示一個(gè)正則表達(dá)式中的\。例如,\d在 Go 字符串中應(yīng)寫(xiě)為\\d。
4. 量詞(Quantifiers)
量詞用于指定其前面的元素(可以是單個(gè)字符、字符類(lèi)或分組)需要匹配的次數(shù):
| 量詞 | 描述 | 示例 |
|---|---|---|
* | 匹配前面的元素 0 次或多次(貪婪模式) | a*匹配 “”、“a”、“aa”、“aaa” 等 |
+ | 匹配前面的元素 1 次或多次(貪婪模式) | a+匹配 “a”、“aa”、“aaa” 等,但不匹配 “” |
? | 匹配前面的元素 0 次或 1 次(可選) | a?匹配 "“或"a” |
{n} | 匹配前面的元素恰好 n 次 | a{3}匹配 “aaa” |
{n,} | 匹配前面的元素至少 n 次(貪婪模式) | a{2,}匹配 “aa”、“aaa”、“aaaa” 等 |
{n,m} | 匹配前面的元素至少 n 次,最多 m 次(貪婪模式) | a{1,3}匹配 “a”、“aa”、“aaa” |
貪婪模式與非貪婪模式:
- 貪婪模式(默認(rèn)):量詞會(huì)盡可能匹配更多的字符
- 非貪婪模式:在量詞后添加
?,表示盡可能匹配更少的字符
示例:
- 對(duì)于字符串 “aaaaa”,
a+(貪婪)會(huì)匹配整個(gè)字符串 - 對(duì)于同樣的字符串,
a+?(非貪婪)會(huì)只匹配第一個(gè) “a” - 對(duì)于字符串 “content1content2”
<div>.*</div>(貪婪)會(huì)匹配整個(gè)字符串<div>.*?</div>(非貪婪)會(huì)匹配第一個(gè)<div>content1</div>
5. 分組與捕獲(Grouping and Capturing)
分組允許我們將多個(gè)元素視為一個(gè)整體,并對(duì)其應(yīng)用量詞或進(jìn)行提?。?/p>
| 表達(dá)式 | 描述 |
|---|---|
(pattern) | 捕獲組:將 pattern 視為一個(gè)整體,并保存匹配結(jié)果 |
(?:pattern) | 非捕獲組:將 pattern 視為一個(gè)整體,但不保存匹配結(jié)果 |
\n | 反向引用:引用第 n 個(gè)捕獲組的匹配結(jié)果(n 為數(shù)字) |
捕獲組示例:
(ab)+匹配 “ab”、“abab”、“ababab” 等(a|b)c匹配 “ac” 或 “bc”(\d{4})-(\d{2})-(\d{2})匹配日期格式,如 “2023-10-05”,并分別捕獲年、月、日
反向應(yīng)用示例:
(\w+)\s+\1匹配重復(fù)的單詞,如 “hello hello”、“test test”<(\w+)>.*?</\1>匹配成對(duì)的 HTML 標(biāo)簽,如<div>...</div>、<p>...</p>
6. 邊界匹配(Boundary Matches)
邊界匹配用于定位字符串中的特定位置:
| 表達(dá)式 | 描述 |
|---|---|
\b | 單詞邊界,匹配單詞的開(kāi)始或結(jié)束位置 |
\B | 非單詞邊界,匹配不在單詞邊界的位置 |
^ | 字符串開(kāi)頭 |
$ | 字符串結(jié)尾 |
示例解析:
\bcat\b匹配獨(dú)立的 “cat”,但不匹配 “category” 或 “scat”\Bcat\B匹配 “category” 中的 “cat”,但不匹配獨(dú)立的 “cat”^Hello只匹配位于字符串開(kāi)頭的 “Hello”World$只匹配位于字符串結(jié)尾的 “World”
7. 邏輯或(Alternation)
使用|表示邏輯或操作,匹配多個(gè)模式中的任意一個(gè):
cat|dog匹配 “cat” 或 “dog”(red|blue|green)匹配 “red”、“blue” 或 “green”I like (tea|coffee|milk)匹配 “I like tea”、“I like coffee” 或 “I like milk”
優(yōu)先級(jí)注意:|的優(yōu)先級(jí)較低,通常需要配合分組使用。例如,a|bc匹配 “a” 或 “bc”,而(a|b)c匹配 “ac” 或 “bc”。
8. 特殊模式
| 表達(dá)式 | 描述 | 示例 |
|---|---|---|
(?i) | 開(kāi)啟不區(qū)分大小寫(xiě)模式 | (?i)hello 匹配 “hello”、“HELLO”、“Hello” 等 |
(?s) | 開(kāi)啟單行模式:使.匹配包括換行符在內(nèi)的任意字符 | (?s)a.*b 匹配 “a\nb” |
(?m) | 開(kāi)啟多行模式:使^和$匹配每行的開(kāi)頭和結(jié)尾 | (?m)^hello 匹配每一行開(kāi)頭的 “hello” |
這些模式可以組合使用,例如(?is)表示同時(shí)開(kāi)啟不區(qū)分大小寫(xiě)和單行模式。
Go 語(yǔ)言中的正則表達(dá)式
Go 語(yǔ)言的標(biāo)準(zhǔn)庫(kù)regexp提供了全面的正則表達(dá)式支持,其實(shí)現(xiàn)基于 Google 的 RE2 引擎,具有以下特點(diǎn):
- 保證線性時(shí)間復(fù)雜度(O (n)),不會(huì)出現(xiàn)某些正則表達(dá)式引擎的指數(shù)級(jí)性能問(wèn)題
- 線程安全,編譯后的正則表達(dá)式可以在多個(gè) goroutine 中安全使用
- 不支持某些 Perl 風(fēng)格的特性,如回溯引用和 lookaround 斷言,但這也保證了其性能優(yōu)勢(shì)
正則表達(dá)式的編譯
在 Go 中使用正則表達(dá)式,首先需要將正則表達(dá)式字符串編譯為一個(gè)*regexp.Regexp對(duì)象。regexp包提供了兩個(gè)主要的編譯函數(shù):
regexp.Compile(pattern string) (*Regexp, error)
該函數(shù)編譯給定的正則表達(dá)式模式,并返回一個(gè)*regexp.Regexp對(duì)象。如果模式無(wú)效,會(huì)返回錯(cuò)誤。
package main
import (
"fmt"
"regexp"
)
func main() {
// 編譯一個(gè)簡(jiǎn)單的正則表達(dá)式
re, err := regexp.Compile(`hello`)
if err != nil {
fmt.Printf("編譯正則表達(dá)式失敗: %v\n", err)
return
}
fmt.Println("正則表達(dá)式編譯成功")
// 使用編譯后的正則表達(dá)式
fmt.Println(re.MatchString("hello world")) // 輸出: true
}
regexp.MustCompile(pattern string) *Regexp
該函數(shù)與Compile類(lèi)似,但在編譯失敗時(shí)會(huì)直接觸發(fā)panic,而不是返回錯(cuò)誤。適用于模式固定且確定有效的情況,通常用于包級(jí)變量的初始化。
package main
import (
"fmt"
"regexp"
)
// 包級(jí)變量,使用MustCompile初始化
var emailRegex = regexp.MustCompile(`^[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}$`)
func main() {
email := "test@example.com"
if emailRegex.MatchString(email) {
fmt.Printf("%s 是有效的郵箱地址\n", email)
} else {
fmt.Printf("%s 是無(wú)效的郵箱地址\n", email)
}
}
最佳實(shí)踐:
- 對(duì)于頻繁使用的正則表達(dá)式,應(yīng)在程序啟動(dòng)時(shí)編譯一次并復(fù)用,避免重復(fù)編譯的開(kāi)銷(xiāo)
- 對(duì)于模式固定的正則表達(dá)式,優(yōu)先使用
MustCompile在包初始化時(shí)創(chuàng)建 - 對(duì)于動(dòng)態(tài)生成的正則表達(dá)式,使用
Compile并妥善處理可能的錯(cuò)誤
匹配操作
*regexp.Regexp類(lèi)型提供了一系列方法用于檢查字符串是否匹配正則表達(dá)式:
MatchString(s string) bool
檢查字符串s是否與正則表達(dá)式匹配。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`) // 匹配一個(gè)或多個(gè)數(shù)字
fmt.Println(re.MatchString("123")) // true
fmt.Println(re.MatchString("abc")) // false
fmt.Println(re.MatchString("abc123")) // true,因?yàn)榘瑪?shù)字
}
Match(b []byte) bool
檢查字節(jié)切片b是否與正則表達(dá)式匹配,功能與MatchString類(lèi)似,但接收字節(jié)切片作為參數(shù)。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`go`)
data := []byte("golang")
fmt.Println(re.Match(data)) // true
}
查找操作
查找操作用于從字符串中尋找與正則表達(dá)式匹配的部分:
FindString(s string) string
返回字符串s中第一個(gè)與正則表達(dá)式匹配的子串。如果沒(méi)有匹配,返回空字符串。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`) // 匹配數(shù)字
s := "abc123def456ghi"
fmt.Println(re.FindString(s)) // 輸出: 123
}
FindStringIndex(s string) []int
返回字符串s中第一個(gè)匹配子串的起始和結(jié)束索引([start, end])。如果沒(méi)有匹配,返回nil。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`)
s := "abc123def456ghi"
indices := re.FindStringIndex(s)
if indices != nil {
fmt.Printf("匹配位置: %d-%d\n", indices[0], indices[1]) // 3-6
fmt.Println("匹配內(nèi)容:", s[indices[0]:indices[1]]) // 123
}
}
FindStringSubmatch(s string) []string
返回一個(gè)切片,包含第一個(gè)匹配的子串及其所有捕獲組的內(nèi)容。切片的第一個(gè)元素是整個(gè)匹配的子串,后續(xù)元素是各個(gè)捕獲組的內(nèi)容。
package main
import (
"fmt"
"regexp"
)
func main() {
// 匹配日期,包含三個(gè)捕獲組:年、月、日
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
s := "今天是2023-10-05,昨天是2023-10-04"
// 查找第一個(gè)匹配
submatches := re.FindStringSubmatch(s)
if submatches != nil {
fmt.Println("完整匹配:", submatches[0]) // 2023-10-05
fmt.Println("年:", submatches[1]) // 2023
fmt.Println("月:", submatches[2]) // 10
fmt.Println("日:", submatches[3]) // 05
}
}
FindStringSubmatchIndex(s string) []int
返回一個(gè)切片,包含第一個(gè)匹配的子串及其所有捕獲組的起始和結(jié)束索引。索引的排列方式為:[整體匹配開(kāi)始, 整體匹配結(jié)束, 第一個(gè)組開(kāi)始, 第一個(gè)組結(jié)束, ...]。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\d{4})-(\d{2})-(\d{2})`)
s := "今天是2023-10-05,昨天是2023-10-04"
indices := re.FindStringSubmatchIndex(s)
if indices != nil {
fmt.Println("索引:", indices) // 輸出: [3 13 3 7 8 10 11 13]
fmt.Println("完整匹配:", s[indices[0]:indices[1]]) // 2023-10-05
fmt.Println("年:", s[indices[2]:indices[3]]) // 2023
fmt.Println("月:", s[indices[4]:indices[5]]) // 10
fmt.Println("日:", s[indices[6]:indices[7]]) // 05
}
}
FindAllString(s string, n int) []string
返回字符串s中所有匹配的子串,最多返回n個(gè)。如果n為負(fù)數(shù),則返回所有匹配項(xiàng)。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`)
s := "abc123def456ghi789"
// 返回所有匹配項(xiàng)
matches := re.FindAllString(s, -1)
fmt.Println("所有匹配:", matches) // 輸出: [123 456 789]
// 只返回前兩個(gè)匹配項(xiàng)
matches = re.FindAllString(s, 2)
fmt.Println("前兩個(gè)匹配:", matches) // 輸出: [123 456]
}
FindAllStringIndex(s string, n int) []int
類(lèi)似FindStringIndex,但返回所有匹配的起始和結(jié)束索引,最多返回n個(gè)。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`)
s := "abc123def456ghi789"
indices := re.FindAllStringIndex(s, -1)
for i, idx := range indices {
fmt.Printf("匹配 %d: 位置 %d-%d, 內(nèi)容 %s\n",
i+1, idx[0], idx[1], s[idx[0]:idx[1]])
}
// 輸出:
// 匹配 1: 位置 3-6, 內(nèi)容 123
// 匹配 2: 位置 9-12, 內(nèi)容 456
// 匹配 3: 位置 15-18, 內(nèi)容 789
}
FindAllStringSubmatch(s string, n int) [][]string
返回所有匹配的子串及其捕獲組的內(nèi)容,最多返回n個(gè)。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`(\w+)-(\d+)`)
s := "item1-123 item2-456 item3-789"
matches := re.FindAllStringSubmatch(s, -1)
for i, match := range matches {
fmt.Printf("匹配 %d: 完整=%s, 組1=%s, 組2=%s\n",
i+1, match[0], match[1], match[2])
}
// 輸出:
// 匹配 1: 完整=item1-123, 組1=item1, 組2=123
// 匹配 2: 完整=item2-456, 組1=item2, 組2=456
// 匹配 3: 完整=item3-789, 組1=item3, 組2=789
}
替換操作
正則表達(dá)式的替換操作允許我們基于匹配結(jié)果修改字符串內(nèi)容:
ReplaceAllString(src, repl string) string
將字符串src中所有匹配的部分替換為repl,返回替換后的新字符串。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`\d+`)
src := "hello123world456"
// 替換所有數(shù)字為X
result := re.ReplaceAllString(src, "X")
fmt.Println("替換結(jié)果:", result) // 輸出: helloXworldX
}
ReplaceAllStringFunc(src string, repl func(string) string) string
將字符串src中所有匹配的部分替換為repl函數(shù)的返回值。
package main
import (
"fmt"
"regexp"
"strconv"
)
func main() {
re := regexp.MustCompile(`\d+`)
src := "hello123world456"
// 將每個(gè)數(shù)字部分轉(zhuǎn)換為其長(zhǎng)度
result := re.ReplaceAllStringFunc(src, func(match string) string {
return strconv.Itoa(len(match))
})
fmt.Println("替換結(jié)果:", result) // 輸出: hello3world3
}
ReplaceAllLiteralString(src, repl string) string
與ReplaceAllString類(lèi)似,但將替換字符串repl視為普通文本,不解析其中的元字符。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`a.b`)
src := "acb aab abb"
// 使用$符號(hào)作為替換文本
result := re.ReplaceAllLiteralString(src, "$1")
fmt.Println("替換結(jié)果:", result) // 輸出: $1 $1 $1
}
分割操作
regexp包還提供了基于正則表達(dá)式的字符串分割功能:
Split(s string, n int) []string
將字符串s按匹配的正則表達(dá)式分割成多個(gè)子串,最多返回n個(gè)子串。如果n為負(fù)數(shù),則返回所有可能的子串。
package main
import (
"fmt"
"regexp"
)
func main() {
re := regexp.MustCompile(`[,\s]+`) // 匹配逗號(hào)或空白字符
s := "hello, world golang,java"
parts := re.Split(s, -1)
fmt.Println("分割結(jié)果:", parts) // 輸出: [hello world golang java]
}
實(shí)際應(yīng)用案例
1. 驗(yàn)證電子郵件地址
電子郵件驗(yàn)證是一個(gè)常見(jiàn)的需求,使用正則表達(dá)式可以快速檢查郵箱格式是否有效。
package main
import (
"fmt"
"regexp"
)
// 驗(yàn)證電子郵件地址的正則表達(dá)式
// 該模式匹配大多數(shù)常見(jiàn)的有效郵箱格式
var emailRegex = regexp.MustCompile(`^[a-z0-9._%+\-]+@[a-z0-9.\-]+\.[a-z]{2,}$`)
func isValidEmail(email string) bool {
return emailRegex.MatchString(email)
}
func main() {
emails := []string{
"test@example.com",
"user.name+tag@domain.co.uk",
"invalid.email",
"@missing_username.com",
"user@domain",
"user@domain..com",
}
for _, email := range emails {
if isValidEmail(email) {
fmt.Printf("%-25s 是有效的郵箱地址\n", email)
} else {
fmt.Printf("%-25s 是無(wú)效的郵箱地址\n", email)
}
}
}
輸出結(jié)果:
test@example.com 是有效的郵箱地址
user.name+tag@domain.co.uk 是有效的郵箱地址
invalid.email 是無(wú)效的郵箱地址
@missing_username.com 是無(wú)效的郵箱地址
user@domain 是無(wú)效的郵箱地址
user@domain..com 是無(wú)效的郵箱地址
2. 提取 URL 中的參數(shù)
從 URL 中提取查詢(xún)參數(shù)是 Web 開(kāi)發(fā)中的常見(jiàn)任務(wù),可以使用正則表達(dá)式來(lái)完成。
package main
import (
"fmt"
"regexp"
)
func main() {
url := "https://example.com/search?query=golang&page=2&limit=10"
// 匹配查詢(xún)參數(shù)的正則表達(dá)式
re := regexp.MustCompile(`([^?&=]+)=([^&]+)`)
// 查找所有匹配的參數(shù)
matches := re.FindAllStringSubmatch(url, -1)
// 打印提取的參數(shù)
fmt.Println("從URL中提取的參數(shù):")
for _, match := range matches {
fmt.Printf("%-10s = %s\n", match[1], match[2])
}
}
輸出結(jié)果:
從URL中提取的參數(shù):
query = golang
page = 2
limit = 10
3. 格式化電話(huà)號(hào)碼
將不規(guī)則的電話(huà)號(hào)碼格式化為統(tǒng)一的格式是另一個(gè)常見(jiàn)的文本處理任務(wù)。
package main
import (
"fmt"
"regexp"
)
func formatPhoneNumber(phone string) string {
// 移除所有非數(shù)字字符
re := regexp.MustCompile(`\D`)
digits := re.ReplaceAllString(phone, "")
// 檢查是否為有效的11位中國(guó)手機(jī)號(hào)
if len(digits) == 11 {
return fmt.Sprintf("%s-%s-%s", digits[0:3], digits[3:7], digits[7:11])
}
// 其他情況返回原始數(shù)字
return digits
}
func main() {
phones := []string{
"13800138000",
"(139)00139000",
"137-0013-7000",
"136 0013 6000",
"123456", // 無(wú)效號(hào)碼
}
for _, phone := range phones {
fmt.Printf("原號(hào)碼: %-15s 格式化后: %s\n", phone, formatPhoneNumber(phone))
}
}
輸出結(jié)果:
原號(hào)碼: 13800138000 格式化后: 138-0013-8000
原號(hào)碼: (139)00139000 格式化后: 139-0013-9000
原號(hào)碼: 137-0013-7000 格式化后: 137-0013-7000
原號(hào)碼: 136 0013 6000 格式化后: 136-0013-6000
原號(hào)碼: 123456 格式化后: 123456
4. 統(tǒng)計(jì)代碼行數(shù)
統(tǒng)計(jì)代碼文件中的有效行數(shù)(不包括空行和注釋?zhuān)┦且粋€(gè)常見(jiàn)的需求,可以使用正則表達(dá)式來(lái)實(shí)現(xiàn)。
package main
import (
"fmt"
"io/ioutil"
"regexp"
"strings"
)
func countLines(code string) int {
// 移除單行注釋
reSingleLineComment := regexp.MustCompile(`//.*$`)
code = reSingleLineComment.ReplaceAllString(code, "")
// 移除多行注釋
reMultiLineComment := regexp.MustCompile(`/\*.*?\*/`)
code = reMultiLineComment.ReplaceAllString(code, "")
// 分割成行
lines := strings.Split(code, "\n")
// 統(tǒng)計(jì)非空行
count := 0
for _, line := range lines {
if strings.TrimSpace(line) != "" {
count++
}
}
return count
}
func main() {
// 示例Go代碼
code := `package main
import (
"fmt"
)
func main() {
// 這是一個(gè)注釋
fmt.Println("Hello, World!") // 打印消息
}
`
lineCount := countLines(code)
fmt.Printf("代碼有效行數(shù): %d\n", lineCount)
}
輸出結(jié)果:
代碼有效行數(shù): 7
5. 提取 HTML 中的鏈接
從 HTML 文檔中提取所有鏈接是爬蟲(chóng)開(kāi)發(fā)中的基礎(chǔ)操作,可以使用正則表達(dá)式實(shí)現(xiàn)這一功能。
package main
import (
"fmt"
"regexp"
)
func extractLinks(html string) []string {
// 匹配<a>標(biāo)簽中的href屬性
re := regexp.MustCompile(`<a\s+(?:[^>]*?\s+)?href="([^" rel="external nofollow" ]*)"`)
// 查找所有匹配項(xiàng)
matches := re.FindAllStringSubmatch(html, -1)
// 提取鏈接
links := make([]string, 0, len(matches))
for _, match := range matches {
links = append(links, match[1])
}
return links
}
func main() {
html := `
<html>
<body>
<a rel="external nofollow" >Google</a>
<a rel="external nofollow" class="link">Example</a>
<a href='#section'>Section</a>
</body>
</html>
`
links := extractLinks(html)
fmt.Println("提取的鏈接:")
for _, link := range links {
fmt.Println(link)
}
}
輸出結(jié)果:
提取的鏈接:
https://www.google.com
http://example.com
#section
性能優(yōu)化與注意事項(xiàng)
在使用正則表達(dá)式時(shí),特別是在處理大量數(shù)據(jù)或高性能場(chǎng)景下,需要注意以下幾點(diǎn)以確保性能和正確性:
1. 預(yù)編譯正則表達(dá)式
正則表達(dá)式的編譯是一個(gè)相對(duì)昂貴的操作,因此應(yīng)盡量避免在循環(huán)中重復(fù)編譯相同的模式。推薦的做法是在包初始化時(shí)使用MustCompile預(yù)編譯正則表達(dá)式。
不推薦的寫(xiě)法:
for _, text := range texts {
re, _ := regexp.Compile(`\d+`) // 每次循環(huán)都編譯
if re.MatchString(text) {
// 處理匹配
}
}
推薦的寫(xiě)法:
var numberRegex = regexp.MustCompile(`\d+`) // 包級(jí)別變量,預(yù)編譯一次
func processTexts(texts []string) {
for _, text := range texts {
if numberRegex.MatchString(text) {
// 處理匹配
}
}
}
2. 選擇合適的函數(shù)
根據(jù)具體需求選擇最適合的函數(shù),避免使用過(guò)于通用的函數(shù)導(dǎo)致不必要的開(kāi)銷(xiāo)。例如:
- 如果只需要檢查是否匹配,使用
MatchString - 如果只需要查找第一個(gè)匹配項(xiàng),使用
FindString而不是FindAllString - 如果需要捕獲組,使用
FindStringSubmatch而不是手動(dòng)解析匹配結(jié)果
3. 避免貪婪匹配導(dǎo)致的回溯
貪婪匹配(如.*)可能會(huì)導(dǎo)致大量的回溯,特別是在處理長(zhǎng)文本時(shí),會(huì)顯著影響性能。應(yīng)盡量使用非貪婪匹配(如.*?)或更具體的模式。
性能較差的模式:
re := regexp.MustCompile(`<.*>`) // 貪婪匹配,可能導(dǎo)致大量回溯
性能較好的模式:
re := regexp.MustCompile(`<[^>]*>`) // 非貪婪匹配,更高效
4. 處理大文本時(shí)的內(nèi)存考慮
當(dāng)處理非常大的文本時(shí),使用FindAllString等函數(shù)可能會(huì)導(dǎo)致內(nèi)存問(wèn)題。此時(shí)可以考慮使用迭代器或流式處理方法:
package main
import (
"fmt"
"regexp"
)
func main() {
// 假設(shè)這是一個(gè)非常大的文本
largeText := "a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6q7r8s9t0u1v2w3x4y5z6"
re := regexp.MustCompile(`\d`)
// 使用FindReaderIndex處理大文本
matches := 0
for loc := re.FindReaderIndex(strings.NewReader(largeText)); loc != nil; {
matches++
// 處理匹配位置loc
// 繼續(xù)從下一個(gè)位置查找
loc = re.FindReaderIndex(strings.NewReader(largeText[loc[1]:]))
}
fmt.Printf("找到 %d 個(gè)匹配項(xiàng)\n", matches)
}
5. 理解 Go 正則表達(dá)式的限制
Go 的正則表達(dá)式基于 RE2 引擎,雖然保證了線性時(shí)間復(fù)雜度,但也有一些限制:
- 不支持回溯引用(如
\1) - 不支持正向 / 負(fù)向預(yù)查(lookahead/lookbehind)
- 不支持遞歸匹配
如果需要這些功能,可以考慮使用第三方庫(kù)如regexp/syntax或go-perl-regexp,但需要注意這些庫(kù)可能不具備 RE2 的性能保證。
常見(jiàn)正則表達(dá)式模式庫(kù)
為了方便使用,這里提供一些常見(jiàn)的正則表達(dá)式模式:
1. 基礎(chǔ)驗(yàn)證
// 驗(yàn)證IP地址
var ipRegex = regexp.MustCompile(`^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$`)
// 驗(yàn)證URL
var urlRegex = regexp.MustCompile(`^(https?|ftp)://[^\s/$.?#].[^\s]*$`)
// 驗(yàn)證中國(guó)手機(jī)號(hào)
var phoneRegex = regexp.MustCompile(`^1[3-9]\d{9}$`)
// 驗(yàn)證日期(YYYY-MM-DD格式)
var dateRegex = regexp.MustCompile(`^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])$`)
// 驗(yàn)證密碼強(qiáng)度(至少8位,包含大小寫(xiě)字母和數(shù)字)
var passwordRegex = regexp.MustCompile(`^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)[a-zA-Z\d]{8,}$`)
2. 文本提取
// 提取HTML標(biāo)簽
var htmlTagRegex = regexp.MustCompile(`<[^>]+>`)
// 提取郵箱地址
var emailExtractRegex = regexp.MustCompile(`[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}`)
// 提取域名
var domainRegex = regexp.MustCompile(`(?i)[a-z0-9][a-z0-9-]{0,61}[a-z0-9](?:\.[a-z]{2,})+`)
// 提取圖片URL
var imageUrlRegex = regexp.MustCompile(`(?i)\b(https?://[^>\s]+?\.(jpg|jpeg|png|gif|bmp))\b`)
3. 文本處理
// 移除HTML標(biāo)簽
func stripHtmlTags(html string) string {
re := regexp.MustCompile(`<[^>]*>`)
return re.ReplaceAllString(html, "")
}
// 移除連續(xù)空格
func removeExtraSpaces(text string) string {
re := regexp.MustCompile(`\s+`)
return re.ReplaceAllString(text, " ")
}
// 轉(zhuǎn)換駝峰命名為蛇形命名
func camelToSnake(s string) string {
re := regexp.MustCompile(`([a-z0-9])([A-Z])`)
return re.ReplaceAllString(s, "${1}_${2}")
}
// 轉(zhuǎn)換蛇形命名為駝峰命名
func snakeToCamel(s string) string {
re := regexp.MustCompile(`_([a-z])`)
return re.ReplaceAllStringFunc(s, func(match string) string {
return strings.ToUpper(match[1:])
})
}
總結(jié)
到此這篇關(guān)于正則表達(dá)式詳解以及Golang中的應(yīng)用示例的文章就介紹到這了,更多相關(guān)Golang正則表達(dá)式應(yīng)用內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
golang?gorm的關(guān)系關(guān)聯(lián)實(shí)現(xiàn)示例
這篇文章主要為大家介紹了golang?gorm的關(guān)系關(guān)聯(lián)實(shí)現(xiàn)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
一文詳解Go語(yǔ)言中對(duì)象池的正確打開(kāi)方式
對(duì)象池是一種設(shè)計(jì)模式,它維護(hù)一組已經(jīng)創(chuàng)建好的對(duì)象,當(dāng)需要使用對(duì)象時(shí),直接從對(duì)象池中獲取,使用完畢后再放回對(duì)象池,而不是頻繁地創(chuàng)建和銷(xiāo)毀對(duì)象,下面我們就來(lái)看看Go語(yǔ)言中對(duì)象池的具體使用吧2025-02-02
Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過(guò)程詳解
這篇文章主要介紹了Go結(jié)合反射將結(jié)構(gòu)體轉(zhuǎn)換成Excel的過(guò)程詳解,大概思路是在Go的結(jié)構(gòu)體中每個(gè)屬性打上一個(gè)excel標(biāo)簽,利用反射獲取標(biāo)簽中的內(nèi)容,作為表格的Header,需要的朋友可以參考下2022-06-06
Go語(yǔ)言中的goroutine和channel如何協(xié)同工作
在Go語(yǔ)言中,goroutine和channel是并發(fā)編程的兩個(gè)核心概念,它們協(xié)同工作以實(shí)現(xiàn)高效、安全的并發(fā)執(zhí)行,本文將詳細(xì)探討goroutine和channel如何協(xié)同工作,以及它們?cè)诓l(fā)編程中的作用和優(yōu)勢(shì),需要的朋友可以參考下2024-04-04
go語(yǔ)言通過(guò)結(jié)構(gòu)體生成json示例解析
這篇文章主要為大家介紹了go語(yǔ)言通過(guò)結(jié)構(gòu)體生成json示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪2022-04-04
golang字符串轉(zhuǎn)Time類(lèi)型問(wèn)題
本文主要介紹了golang字符串轉(zhuǎn)Time類(lèi)型問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04

