正則是用於處理文字的利器之一。
關於正則的基礎知識及應用,之前寫過幾篇文章,讀者可以閱讀文後的相關資料作一基本瞭解。本文主要學習 Go 的正則。
正規表示式學習,可以分為三個子部分:
- 正則 API ;
- 正則語法 ;
- 正則匹配策略。
正則 API
第一個要學習的,就是 Go 正則 API。 API 是通往程式世界的第一把鑰匙。
學習 API 的最簡單方式,就是在電腦上敲下程式,然後看程式輸出。根據 AI 給出的例子,自己加以改造和嘗試,深化理解。
基礎 API
import (
"fmt"
"regexp"
"testing"
)
func TestGoRegex(t *testing.T) {
// 建立一個新的正規表示式物件
pattern := "^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$"
r, err := regexp.Compile(pattern)
if err != nil {
fmt.Println(err)
}
fmt.Println(r.String()) // ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+.[a-zA-Z]{2,}$
fmt.Println(r.MatchString("qinshu@163.com")) // true
// 建立原生字串並查詢字串
enclosedInt := regexp.MustCompile(`[\[{(]\d+[)}\]]`)
matches := enclosedInt.FindAllString("(12) [34] {56}", -1)
fmt.Println(matches) // [(12) [34] {56}]
// 有限次數匹配
matches2 := enclosedInt.FindAllString("(12) [34] {56}", 2)
fmt.Println(matches2) // [(12) [34]]
// 匹配的索引位置
matchIndexes := enclosedInt.FindAllStringIndex("(12) [34] {56}", -1)
fmt.Println(matchIndexes) // [[0 4] [5 9] [10 14]] 右邊的索引位置是不包含的
matchIndexes2 := enclosedInt.FindAllStringIndex("(12) [34] {56}", 2)
fmt.Println(matchIndexes2) // [[0 4] [5 9]] 右邊的索引位置是不包含的
// 替代
spacePattern := regexp.MustCompile(`\s+`)
origin := "hello world! \n You get champion!"
replaced := spacePattern.ReplaceAllString(origin, " ")
fmt.Println(replaced)
}
正則捕獲
捕獲並提取由正規表示式提取的文字,是日常開發常備的一個子任務。捕獲需要透過 () 括起來的內容。比如 (\d+) 就會捕獲 \d+ 匹配的文字。
func TestRegexCatch(t *testing.T) {
input := "(12) [34] {56}"
pattern := `\((\d+)\) \[(\d+)\] \{(\d+)\}`
re := regexp.MustCompile(pattern)
submatches := re.FindStringSubmatch(input)
numbers := make([]string, 0)
for i := 1; i < len(submatches); i++ {
numbers = append(numbers, submatches[i])
}
fmt.Println("Captured numbers:", numbers)
}
正則反向引用
正規表示式中的反向引用是一種機制,它允許你在同一個正規表示式中引用先前已捕獲的分組內容。捕獲組是透過圓括號 ()
定義的,當正規表示式引擎遇到捕獲組併成功匹配其中的內容時,該內容會被記住並在後續匹配過程中被引用。引用的方式通常是透過反斜槓 \
加上一個或多個數字,數字代表被捕獲組的順序(從左到右,從1開始計數)。
反向引用一般用來匹配成對的標籤。比如,將 <標籤>文字</標籤> 中的文字提取出來,如下:
@Test
public void testBackReference() {
Pattern p = Pattern.compile("(?i)<([a-z][a-z0-9]*)[^>]*>(.*?)<\\/\\1>");
Matcher match = p.matcher("<h1>我是大標題</h1>");
if (match.find()) {
System.out.println(match.group(2));
}
}
不過 Go 並不支援反向引用的語法。
正則語法
關於正則語法,最需要了解的是 POSIX 語法。
- Go 的正則有反引號 ``, 可以建立原生字串,不用像 Java 那樣總要加兩道斜槓,這樣使得正規表示式更清晰。比如 java 版的 enclosedInt 得寫成這樣:
"[(\\[{]*\\d+[)\\]}]*"
如果有原生斜槓,還得再加兩道斜槓。Go 只要寫成
`[\[{(]\d+[)}\]]`
正則匹配策略
正則匹配有兩種最常用的匹配策略:
Leftmost-First Match(最左優先匹配但非最長)
正規表示式匹配的一種策略,也稱為“最左優先匹配”。在處理文字時,這種匹配策略會從目標文字的左側開始搜尋,一旦找到第一個能夠滿足正規表示式的子串,就立即停止進一步的搜尋,並返回該匹配結果。即使可能存在更長的匹配子串,也會優先返回最先找到的匹配。
在正規表示式中透過在重複元字元後面新增 ?
來實現,如 *?
、+?
、??
。在這一策略下,引擎從左到右搜尋,但在遇到重複元字元時,它會盡可能少地消耗文字,也就是說,只要滿足匹配條件,它就會立即停止匹配更多的字元。
func TestRegexLeftMostFirstMatch(t *testing.T) {
text := "abccc"
re := regexp.MustCompile("ab(c)+?")
matches := re.FindAllString(text, -1)
fmt.Println(matches) // [abc]
}
Leftmost-Longest Match(最長/最左優先匹配)
也稱為“貪心匹配”,這是許多正規表示式引擎(如Perl、Python、JavaScript、PHP、Java等)的預設匹配策略。在這種策略下,正規表示式引擎從左到右逐字元地搜尋文字,一旦找到一個符合模式的匹配,它會選擇最長可能的匹配,也就是說,對於重複元字元(如 *
、+
、?
和 {m,n}
)它會盡可能多地消耗文字。
func TestRegexLeftMostLongestMatch(t *testing.T) {
text := "abccc"
re := regexp.MustCompile("ab(c)+")
matches := re.FindAllString(text, -1)
fmt.Println(matches) // [abccc]
}
此外,還有些特定匹配模式:
Anchored Matching(錨定匹配)
當正規表示式以 ^
(開始位置)或 $
(結束位置)等定位符開始或結束時,匹配只能在字串的開始或結束處進行,這意味著匹配時強制考慮字串的邊界。
func TestRegexAnchorMatch(t *testing.T) {
text := "abccc"
re := regexp.MustCompile("^ab?c+$")
matches := re.FindAllString(text, -1)
fmt.Println(matches) // [abccc]
re2 := regexp.MustCompile("^bc+$")
nomatch := re2.FindAllString(text, -1)
fmt.Println(nomatch) // []
}
**Multiline Matching(多行匹配)**
在多行模式下,正規表示式中的 ^
和 $
除了匹配字串的開始和結束外,還可以匹配每一行的開始和結束。Go 預設支援多行模式。
func TestMultiLineMatch(t *testing.T) {
text := `Line 1
start
Middle line 1
Middle line 2
end
Line 3`
pattern := regexp.MustCompile(`start([\s\S]*?)end`)
matches := pattern.MatchString(text)
fmt.Println(matches) // true
}
func TestMultiLineMatch3(t *testing.T) {
text := `start
Middle line 1
Middle line 2
end`
pattern := regexp.MustCompile(`^start([\s\S]*?)end$`)
matches := pattern.MatchString(text)
fmt.Println(matches) // true
}
func TestMultiLineMatch2(t *testing.T) {
text := `Line 1
start
Middle line 1
Middle line 2
end
Line 3`
pattern := regexp.MustCompile(`^start([\s\S]*?)end$`)
matches := pattern.MatchString(text)
fmt.Println(matches) // false
}
Singleline Matching(單行匹配)
在單行模式下,.
元字元可以匹配包括換行符在內的所有字元,而在普通模式下,.
不匹配換行符。Go 不支援單行模式匹配。
func TestSingleLineMatch3(t *testing.T) {
text := `start
Middle line 1
Middle line 2
end`
pattern := regexp.MustCompile(`^start.*end$`)
matches := pattern.MatchString(text)
fmt.Println(matches) // false
}
Backtracking(回溯匹配)
在處理複雜的正規表示式時,引擎可能採用回溯演算法,嘗試不同的路徑來找到匹配。當正規表示式包含分支結構(如 (|)
)和重複結構時,引擎會嘗試所有可能的匹配路徑,直至找到一個匹配或確定無匹配。
Go 採用 RE2 (DFA)實現,不支援回溯匹配。
Atomic Grouping and Possessive Quantifiers(原子組和佔有量詞)
一些正規表示式引擎支援原子組 (?>...)
和佔有量詞,這些機制有助於控制回溯行為,以提高匹配效率和準確性。Go 也不支援原子組和佔有量詞。
相關文章
- 正規表示式基礎知識
- 使用正規表示式抽取所需文字
- 使用Python從Markdown文件中自動生成標題導航
- Python正則處理多行日誌一例
- Scala正則和抽取器:解析方法引數