
餘晟著
目前國內IT領域用到搜索的場景有很多,這樣也就自然而然涉及了正則表達式的應用。作者將開發中遇到的實例,以及其他人諮詢的問題,總結出一套正則表達式解題的辦法,並通過一些具體的例子,講解使用,能指導讀者拆解、分析問題,另外也有關於效率的經驗總結。本書分爲三大部分,共17章,2個附錄。其中不但闡述了正則表達式的基數應用,還涉及正則表達式的匹配原理、邏輯問題、常見操作和效率問題。
《正則指引》拆解 作者:餘晟 一、一句話概括 寫正則表達式不是憑感覺堆疊符號,而是先把需求拆成字符範圍、位置、重複次數和上下文約束,再根據引擎的匹配與回溯原理構造既正確又高效的模式。 二、核心邏輯 全書分爲三部分: 1. 基礎語法:字符、量詞、括號、斷言、匹配模式及其他常用特性。2. 原理與解題:正則引擎怎樣匹配、遇到問題怎樣拆解、如何避免效率陷阱。3. 編程語言實踐:Java、JavaScript、PHP、Python、Ruby和Linux/UNIX中的正則差異。 核心路徑可以概括爲: 明確需求 → 劃分字符與位置 → 寫出基本結構 → 增加重複和分支 → 加入上下文約束 → 用正反例測試 → 檢查回溯與性能 → 根據語言引擎調整 三、核心內容 1. 正則表達式描述的是文本模式 正則表達式不是一段普通文本,而是一種用於描述字符串集合的小型語言。 它常用於: 驗證格式;搜索文本;提取字段;替換內容;切分字符串;清洗日誌;批量重構;語法高亮;簡單詞法分析。 使用前要先確定任務是“查找”“提取”“替換”還是“驗證”,因爲對應正則的邊界要求不同。 2. 普通字符與元字符必須區分 大多數字符表示自身,某些符號具有特殊意義,例如: . 匹配任意字符;*、+、? 表示重複;[] 表示字符組;() 表示分組;| 表示分支;^、$ 表示位置;反斜槓用於轉義。 如果要匹配元字符本身,通常需要轉義。 正則字符串還可能經過編程語言字符串層的轉義,因此要區分“正則轉義”和“字符串轉義”。 3. 字符組表示某個位置允許出現哪些字符 字符組如[abc]表示當前位置可以是a、b或c。 範圍如[0-9]表示數字,否定字符組如[^0-9]表示非數字字符。 常用簡寫包括: d:數字;w:單詞字符;s:空白字符。 不同引擎和Unicode模式對“數字”“單詞字符”的定義可能不同,不能永遠假設只包含ASCII。 4. 點號的含義受模式影響 . 通常匹配除換行之外的任意字符。 啓用單行或DOTALL模式後,它可能匹配換行。 如果想表示真正的任意字符,也可以根據引擎使用更明確的寫法。 過度使用.*會讓匹配範圍過寬、錯誤難以發現,並可能帶來嚴重回溯。 5. 位置錨點不消耗字符 ^和$匹配字符串或行的開頭、結尾,它們描述位置,而不是具體字符。 格式驗證通常需要完整錨定。例如,只寫一個日期片段模式,可能在包含額外垃圾字符的字符串中也匹配成功。 不同引擎還可能提供絕對字符串開頭和結尾錨點,避免多行模式改變^和$的語義。 6. 單詞邊界也是一種位置條件 通常表示單詞字符與非單詞字符之間的位置。 它適合匹配完整英文單詞,避免在更長單詞內部命中。 但在中文、混合語言和Unicode文本中,單詞邊界行爲可能不符合自然語言的“詞”。 正則邊界是字符分類結果,不是真正的分詞器。 7. 量詞描述重複次數 常見量詞包括: ?:零次或一次;*:零次或多次;+:一次或多次;{m}:恰好m次;{m,n}:m到n次;{m,}:至少m次。 量詞作用於緊鄰的字符、字符組或分組。 設計量詞時應儘量給出合理上下限,無邊界的重複更容易誤匹配和產生性能問題。 8. 貪婪量詞先儘可能多地匹配 大多數普通量詞默認是貪婪的。 例如,.*會先嚐試吞下儘可能多的字符,如果後續無法匹配,再逐步吐出字符回溯。 貪婪並不等於永遠匹配最長結果,而是在當前正則結構和引擎規則下優先嚐試更多字符。 理解“先多取、失敗後回退”,是理解許多意外結果的關鍵。 9. 懶惰量詞先嚐試儘可能少地匹配 在量詞後加?,通常把它變成懶惰量詞,例如.*?。 它先取得儘可能少的字符,只有後續無法匹配時才逐步擴展。 懶惰量詞適合限制區間,但並不是性能優化萬能藥。它同樣可能反覆嘗試和回溯。 更可靠的方法通常是使用明確排除終止符的字符組。 10. 佔有量詞和原子分組可以阻止回溯 部分引擎支持佔有量詞或原子分組。 它們一旦匹配一段文本,就不允許引擎退回重新分配。 這可以: 提高性能;防止錯誤回溯路徑;明確表達某段匹配不可撤銷。 但使用不當也可能讓本來可以成功的表達式失敗。 是否支持這些特性,要根據具體語言和引擎確認。 11. 分組控制量詞和分支範圍 圓括號可以把多個元素組合爲一個整體。 例如,對整個單詞或子模式應用量詞時,需要先分組。 分組也會影響|的作用範圍。若不加括號,分支可能覆蓋比預期更大的部分。 複雜正則應通過分組明確層次,而不是依賴讀者猜測運算優先級。 12. 捕獲組用於提取匹配內容 普通括號通常會記錄內部匹配的文本。 捕獲組可用於: 提取日期的年、月、日;獲取鍵和值;在替換中重新組合;在程序中訪問子匹配;供後續反向引用使用。 如果分組只爲了控制結構,不需要提取,應優先使用非捕獲組,減少編號變化和不必要開銷。 13. 命名分組提高可讀性 與數字編號相比,命名分組可以用有意義的名稱訪問內容。 例如,日期可以命名爲year、month和day。 命名能夠: 減少組編號錯位;提高代碼可維護性;讓替換模板更清晰;支持複雜解析。 不同語言的命名語法和引用方式可能不同,需要查閱對應文檔。 14. 反向引用要求後文重複前面實際匹配的內容 反向引用不是再次執行組內的正則,而是匹配捕獲組已經得到的具體文本。 它可以用於: 查找重複單詞;匹配成對引號;檢查分隔符一致;識別重複片段。 反向引用增強了表達能力,也可能顯著增加回溯複雜度。部分DFA型工具不支持它。 15. 分支表示多個候選模式 A|B表示嘗試A或B。 許多回溯型引擎會按書寫順序嘗試分支,因此順序可能影響結果和性能。 公共前綴可以提取到分支之外,減少重複。 分支過多時,正則會變得難以維護,可能更適合使用代碼、字典或解析器。 16. 斷言檢查上下文但不消耗文本 先行斷言和後行斷言用於判斷當前位置前後是否滿足條件。 常見類型包括: 正向先行:後面必須出現某模式;負向先行:後面不能出現某模式;正向後行:前面必須出現某模式;負向後行:前面不能出現某模式。 斷言適合表達“前面是什麼”“後面不能是什麼”等上下文限制。 後行斷言在不同引擎中的支持和長度限制差異較大。 17. 斷言可以組合多個獨立條件 驗證字符串時,可以在同一位置疊加多個先行斷言。 例如,密碼規則可能分別要求: 至少一個數字;至少一個字母;不包含空格;總長度在範圍內。 斷言讓各條件相互獨立,但過度疊加會降低可讀性,並可能造成性能問題。 複雜業務驗證通常應由正則和程序邏輯共同完成。 18. 匹配模式會改變相同正則的行爲 常見模式包括: 忽略大小寫;多行模式;單行/DOTALL模式;自由間距或註釋模式;Unicode模式。 多行模式通常改變^和$,單行模式則改變.。 自由間距模式可以把複雜正則拆成多行並添加註釋,但空格本身的匹配方式也會改變。 使用模式時,應在代碼或文檔中明確標註。 19. 回溯型引擎按照嘗試路徑工作 Java、JavaScript、Python、PHP等常見環境多使用回溯型正則引擎。 引擎通常從某個位置開始,按照正則順序嘗試: 1)當前元素匹配;2)繼續下一個元素;3)後續失敗;4)回到之前可選擇的位置;5)改用另一種長度或分支;6)重新嘗試。 正則不是一次性“理解”整個字符串,而是在可能路徑中搜索。 20. NFA和DFA引擎具有不同特徵 簡化而言: 回溯型NFA重視表達式順序,支持捕獲、反向引用和斷言等豐富功能;DFA通常並行維護狀態,匹配性能更穩定,但功能可能受限。 不同工具即使都稱爲正則表達式,也可能採用不同引擎和語法。 優化和兼容性判斷必須知道實際使用的引擎。 21. 災難性回溯來自模糊路徑爆炸 當多個可重複結構可以用許多方式劃分同一文本時,引擎可能嘗試指數級組合。 危險結構常包括: 嵌套量詞;重疊分支;寬泛的.*;可爲空的重複項;失敗發生在很後面;對長輸入缺少邊界。 這不僅導致程序緩慢,還可能形成正則拒絕服務漏洞。 22. 優化正則首先要減少不確定性 常見優化方法包括: 使用更具體的字符組;增加明確錨點;限制量詞範圍;避免嵌套的模糊重複;把容易失敗的條件提前;提取公共前綴;使用原子分組或佔有量詞;將複雜任務拆成多步;選擇性能更可控的引擎。 優化不是把正則寫得更短,而是減少引擎需要嘗試的路徑。 23. 解題應從自然語言需求開始拆分 面對一個匹配問題,可以依次詢問: 1)要匹配的對象是什麼?2)字符範圍是什麼?3)哪些部分必須出現?4)哪些部分可選?5)重複多少次?6)開頭和結尾有什麼要求?7)前後文有什麼限制?8)是否需要提取子內容?9)有哪些合法與非法示例?10)輸入長度和性能要求是什麼? 先寫出結構,再逐步增加細節,比一次寫出複雜表達式更可靠。 24. 驗證、查找和提取不能混爲一談 同一個業務對象,在不同任務中需要不同正則。 查找:允許在大段文本中找到局部;驗證:通常要求完整字符串符合規則;提取:需要設計捕獲組;替換:需要考慮替換模板和組引用;切分:關注分隔符和空字段。 如果用查找正則進行驗證,常會接受額外字符。 25. 正則並不適合解析所有結構 正則適合局部、規則相對穩定的文本模式。 對於深度嵌套、完整編程語言、複雜HTML和需要語義狀態的格式,正則往往難以安全維護。 這類任務更適合: 專用解析器;DOM;詞法和語法分析器;JSON/XML庫;分步驟程序邏輯。 “能用正則勉強完成”不等於“應該用正則完成”。 26. 郵箱、URL和日期驗證應明確業務邊界 現實格式可能比簡單示例複雜得多。 例如,完整郵箱規範支持許多少見形式;日期還需要檢查月份和閏年;URL包含編碼、端口和國際化域名。 實際產品應先決定需要支持的範圍,再選擇: 簡化正則;標準庫;解析器;二次業務驗證。 正則適合先篩選格式,不一定負責全部語義正確性。 27. Java中的反斜槓通常需要雙重轉義 在Java源代碼中,正則首先是字符串字面量,再交給正則引擎。 因此,一個正則反斜槓通常需要在字符串中寫成雙反斜槓。 Java提供Pattern與Matcher等API,並支持分組、查找和替換。 工程中應預編譯重複使用的模式,避免每次請求重新編譯。 28. JavaScript正則適合瀏覽器與Node.js文本處理 JavaScript可以使用正則字面量或RegExp構造器。 兩種方式的轉義層次不同:構造器參數作爲字符串時需要額外轉義。 現代JavaScript逐步增加命名組、後行斷言和Unicode等支持,但舊運行環境可能不兼容。 全局模式還可能改變lastIndex狀態,重複使用時需要注意。 29. PHP的preg系列通常基於PCRE PHP常用preg_match、preg_replace和preg_split等函數。 模式通常需要分隔符,並可以添加模式修飾符。 PCRE功能豐富,支持多種斷言、原子組和命名組。 在Web環境中,必須限制輸入長度和正則複雜度,防止回溯型拒絕服務。 30. Python的re模塊強調原始字符串 Python中常使用原始字符串書寫正則,減少字符串轉義干擾。 常用操作包括: search:查找;match:從開頭匹配;fullmatch:完整匹配;findall/finditer:批量查找;sub:替換;split:切分。 應注意Python標準re與第三方regex模塊並非完全相同。 31. Ruby把正則深度融入字符串處理 Ruby提供正則字面量和便捷的匹配、替換接口。 捕獲組和全局匹配狀態使用方便,但隱式狀態可能降低可讀性。 複雜項目中,仍應把模式命名、添加測試,並避免散落在業務代碼中。 32. Linux/UNIX工具存在多種正則方言 grep、sed、awk等工具使用的正則語法並不完全相同。 需要區分: 基本正則表達式;擴展正則表達式;PCRE兼容模式;工具自身的轉義規則。 把某語言中的正則直接複製到命令行,可能因方言不同而失敗。 使用前應確認工具選項和實際引擎。 33. 正則必須使用正反例測試 測試集至少應包含: 最簡單合法輸入;最長合法輸入;每個可選分支;邊界值;空字符串;多行和Unicode;看似接近但非法的輸入;超長惡意輸入;可能觸發回溯的輸入。 只有合法示例通過,不能證明正則正確;還要驗證它能拒絕非法數據,並在長輸入上保持合理性能。 34. 可讀性是正則質量的重要組成 可維護正則應採用: 命名;註釋;非捕獲組;命名捕獲組;自由間距模式;分步驟匹配;配套測試;業務規則說明。 如果一個表達式只有作者本人能看懂,它就是未來的維護風險。 四、本書最值得關注的核心觀點 本書最值得關注的觀點是: 正則表達式的性能和正確性,取決於你給引擎留下了多少種可能的嘗試路徑。 .*、嵌套量詞和重疊分支之所以危險,不是因爲符號本身,而是因爲同一段文本可以被許多方式劃分;當末尾匹配失敗時,引擎會回頭嘗試大量組合。 因此,優秀正則不是最短的正則,而是邊界清晰、歧義較少、便於解釋和測試的正則。 五、一段話總結 《正則指引》從字符組、轉義、量詞、分組、捕獲、反向引用、分支、錨點、單詞邊界和前後行斷言入手,進一步解釋貪婪、懶惰、佔有量詞以及NFA回溯型引擎的匹配過程,並總結驗證、查找、提取、替換和切分等常見任務的拆解方法。全書最重要的實踐原則,是先用自然語言明確字符、位置、次數和上下文,再逐步寫正則,並用合法、非法、邊界和惡意長輸入測試;性能優化則要減少重疊分支、寬泛通配和嵌套量詞造成的回溯路徑。Java、JavaScript、PHP、Python、Ruby和Linux工具在語法、轉義和功能上並不完全相同,因此正則必須根據實際引擎編寫。正則是強大的文本模式工具,但複雜嵌套和完整結構解析應交給專用解析器。
內容很細,從易到難,循序漸進,語言部分總結其實可以挑選自己需要的看,看太多容易混淆,linux部分可以一看。附錄的例子走馬觀花的看一遍即可,自己寫出來纔是最好的。花了20個小時終於讀完了,主要是一些概念,需要記得太多,有些例子看着也會暈乎,但堅持讀下去,就會慢慢都理解。書中唯一的缺點,部分內容重複,或感覺介紹的過多,比如語言部分佔了半本書了,而且連同一語言的不同版本差異都有介紹,太細了,我認爲這裏有點過分細緻了。不過 還是值得一讀,一定要堅持讀。
對正則講的非常全面,由淺入深 作者講解的很細緻初學者都能看懂
工具類書。還是實踐爲好!不過也學了點東西
如果你剛接觸正則表達式,感覺無從下手,那就快來讀一下這本書吧!
看了一二部分的正則表達式原理及第三部分python中正則的用法,正則表達式的內容幾乎面面俱到,知識點也講解得非常透徹。自己日常工作中處理文本經常用到正則表達式,看此書時才感覺之前對正則表達式的理解只是九牛一毛。個人不推薦新手從此書開始學習正則表達式,內容太多會抓不住重點。
這本書非常不錯,也很少見,其中提出的不同情況下的正則表達式的推演我很推崇。後面針對不同語言的部分沒看,後面用到的時候在翻
日本人的書要麼囉嗦得很要麼細膩得很 這本屬於後者
這本書教會我如何思考並構造正則表達式
很全面的講解了正則表達式的用法!後續慢慢的用上[微笑]