式實(shí)戰(zhàn):從基礎(chǔ)語法到高級(jí)應(yīng)用與性能優(yōu)化)
1. 項(xiàng)目概述為什么正則表達(dá)式是每個(gè)Python開發(fā)者的必修課如果你經(jīng)常和文本數(shù)據(jù)打交道無論是從網(wǎng)頁上抓取信息、清洗混亂的日志文件還是驗(yàn)證用戶輸入的郵箱格式一定遇到過這樣的時(shí)刻面對(duì)一大段字符串你需要精準(zhǔn)地找到、提取或替換其中的某些特定部分。用普通的字符串方法比如find、split、replace逐個(gè)字符去匹配不僅代碼冗長而且面對(duì)稍微復(fù)雜一點(diǎn)的模式就力不從心。這時(shí)候你就需要請(qǐng)出文本處理領(lǐng)域的“瑞士軍刀”——正則表達(dá)式。正則表達(dá)式常簡寫為 regex 或 re它并不是 Python 獨(dú)有的而是一套獨(dú)立、強(qiáng)大的微型語言專門用于描述字符串的匹配模式。你可以把它理解為一套極其精密的“文本模具”只要文本能符合這個(gè)模具的形狀就能被匹配出來。在 Python 中通過內(nèi)置的re模塊我們可以方便地調(diào)用這套工具。我見過太多新手開發(fā)者對(duì)它望而生畏選擇繞道而行用一堆脆弱的if-else和循環(huán)來勉強(qiáng)應(yīng)付。但事實(shí)上一旦掌握了它的核心思想其帶來的效率提升是顛覆性的。這篇文章我就結(jié)合自己多年處理文本數(shù)據(jù)的實(shí)戰(zhàn)經(jīng)驗(yàn)帶你從“敬畏”到“駕馭”正則表達(dá)式內(nèi)容會(huì)非常詳細(xì)建議你先收藏需要時(shí)隨時(shí)查閱。2. 正則表達(dá)式核心思想與語法精講正則表達(dá)式的核心在于“模式匹配”。它不是去匹配一個(gè)固定的字符串而是去匹配符合某種規(guī)則的一類字符串。學(xué)習(xí)它的關(guān)鍵不是死記硬背所有符號(hào)而是理解其構(gòu)成元素普通字符和元字符。2.1 元字符構(gòu)建模式的“積木”元字符是擁有特殊功能的字符。下面這些是最常用、必須掌握的基石.點(diǎn)號(hào)匹配除換行符\n以外的任意單個(gè)字符。例如模式a.c可以匹配abc、a c、ac。注意如果需要匹配包括換行符在內(nèi)的任何字符可以使用[\s\S]或開啟re.DOTALL標(biāo)志。^和$分別匹配字符串的開始和結(jié)束。它們用于“錨定”位置而不是匹配具體字符。^abc匹配以abc開頭的字符串。xyz$匹配以xyz結(jié)尾的字符串。^abc$精確匹配整個(gè)字符串就是abc。*,,?量詞控制前面一個(gè)字符或分組的重復(fù)次數(shù)。ab*匹配a后面跟著 0 個(gè)或多個(gè)b如a,ab,abb。ab匹配a后面跟著 1 個(gè)或多個(gè)b如ab,abb但不匹配a。ab?匹配a后面跟著 0 個(gè)或 1 個(gè)b如a,ab。{}精確量詞更精確地控制重復(fù)次數(shù)。a{3}精確匹配aaa。a{3,}匹配至少 3 個(gè)a。a{3,5}匹配 3 到 5 個(gè)a。[]字符集匹配方括號(hào)內(nèi)的任意一個(gè)字符。[abc]匹配a,b, 或c。[a-z]匹配任意小寫字母。[^abc]匹配除了a,b,c之外的任意一個(gè)字符^在方括號(hào)內(nèi)表示“非”。|管道符表示“或”關(guān)系。例如cat|dog匹配cat或dog。\反斜杠轉(zhuǎn)義字符。如果你想匹配元字符本身比如匹配一個(gè)真實(shí)的點(diǎn)號(hào).就需要寫成\.。它也是預(yù)定義字符集的引導(dǎo)符。2.2 預(yù)定義字符集與貪婪模式為了書寫方便正則表達(dá)式用反斜杠加字母定義了一些常用的字符集\d匹配任意數(shù)字等價(jià)于[0-9]。\D匹配任意非數(shù)字等價(jià)于[^0-9]。\w匹配字母、數(shù)字、下劃線等價(jià)于[a-zA-Z0-9_]。注意它通常能匹配大多數(shù)語言的單詞字符。\W匹配非字母、數(shù)字、下劃線。\s匹配任意空白字符包括空格、制表符、換行符等。\S匹配任意非空白字符。貪婪與非貪婪模式非常重要這是新手最容易踩坑的地方。默認(rèn)情況下量詞*,,?,{m,n}是“貪婪”的它們會(huì)盡可能多地匹配字符。在量詞后面加上一個(gè)?就變成了“非貪婪”或“最小匹配”模式它會(huì)盡可能少地匹配字符。舉個(gè)例子對(duì)于字符串divcontent1/divdivcontent2/div貪婪模式r‘div.*/div’會(huì)匹配從第一個(gè)div到最后一個(gè)/div之間的所有內(nèi)容。非貪婪模式r‘div.*?/div’會(huì)匹配divcontent1/div然后繼續(xù)匹配下一個(gè)divcontent2/div。在爬蟲提取標(biāo)簽內(nèi)容時(shí)非貪婪模式幾乎是標(biāo)配。2.3 分組與捕獲用小括號(hào)()括起來的部分就是一個(gè)分組它有兩個(gè)主要作用將多個(gè)字符視為一個(gè)整體以便對(duì)其應(yīng)用量詞。例如(abc)可以匹配abc、abcabc。捕獲匹配到的子串供后續(xù)使用。這是正則表達(dá)式強(qiáng)大功能的關(guān)鍵。捕獲到的內(nèi)容可以通過序號(hào)從1開始在匹配后引用或在替換時(shí)使用。例如模式r‘(\d{4})-(\d{2})-(\d{2})’匹配日期2023-10-01那么\1對(duì)應(yīng)2023\2對(duì)應(yīng)10\3對(duì)應(yīng)01。非捕獲分組如果你只想用括號(hào)分組但不希望捕獲它以節(jié)省內(nèi)存和避免干擾引用順序可以使用(?:...)語法。例如r‘(?:abc|def)’這個(gè)(?:)內(nèi)的內(nèi)容不會(huì)被單獨(dú)捕獲。3. Python re模塊核心方法實(shí)戰(zhàn)解析理解了語法我們來看看在 Python 里怎么用。re模塊提供了幾個(gè)核心函數(shù)它們的區(qū)別主要在于使用場景和返回值。3.1 re.match() 與 re.search()單次匹配re.match(pattern, string, flags0)從字符串的起始位置開始匹配。如果起始位置不符合則匹配失敗返回None。它只匹配一次。import re result re.match(r‘\d‘, ‘123abc‘) # 匹配成功因?yàn)殚_頭是數(shù)字 print(result.group()) # 輸出123 result re.match(r‘\d‘, ‘a(chǎn)bc123‘) # 匹配失敗因?yàn)殚_頭不是數(shù)字 print(result) # 輸出Nonematch適合做嚴(yán)格的格式驗(yàn)證比如檢查字符串是否以某種模式開頭。re.search(pattern, string, flags0)掃描整個(gè)字符串返回第一個(gè)成功的匹配。不要求從開頭開始。result re.search(r‘\d‘, ‘a(chǎn)bc123def456‘) print(result.group()) # 輸出123 (第一個(gè)匹配到的數(shù)字串)當(dāng)你只需要找到字符串中第一個(gè)符合模式的部分時(shí)用search。匹配對(duì)象Match Objectmatch和search成功時(shí)返回一個(gè)匹配對(duì)象。常用的方法有.group()返回匹配的整個(gè)字符串。group(1),group(2)... 返回第1、2...個(gè)捕獲分組的內(nèi)容。.start(),.end()返回匹配在字符串中的起止位置。.span()返回一個(gè)元組(start, end)。3.2 re.findall() 與 re.finditer()多次匹配re.findall(pattern, string, flags0)返回字符串中所有非重疊匹配的列表。如果模式中有分組則返回分組元組的列表。# 無分組返回所有匹配的完整字符串列表 re.findall(r‘\d‘, ‘a(chǎn)1b22c333‘) # 返回[‘1‘, ‘22‘, ‘333‘] # 有分組只返回分組捕獲的內(nèi)容的元組列表 re.findall(r‘(\d)([a-z])‘, ‘123abc 456def‘) # 返回[(123, abc), (456, def)]實(shí)操心得findall在爬蟲中提取所有鏈接、所有價(jià)格數(shù)字時(shí)非常高效。但務(wù)必注意分組行為如果不希望它只返回分組內(nèi)容可以對(duì)整個(gè)模式再加一個(gè)分組或者使用finditer。re.finditer(pattern, string, flags0)返回一個(gè)迭代器其中每個(gè)元素都是一個(gè)匹配對(duì)象。這在處理大量匹配或需要每個(gè)匹配的詳細(xì)信息如位置時(shí)非常有用更節(jié)省內(nèi)存。for match in re.finditer(r‘\d‘, ‘a(chǎn)1b22c333‘): print(match.group(), match.span()) # 輸出 # 1 (1, 2) # 22 (3, 5) # 333 (6, 9)3.3 re.sub() 與 re.subn()替換與分割re.sub(pattern, repl, string, count0, flags0)將字符串中所有匹配模式的部分替換為repl。count參數(shù)指定最大替換次數(shù)0表示全部替換。repl可以是字符串也可以是一個(gè)函數(shù)。# 簡單字符串替換 text “Today is 2023-10-01.“ new_text re.sub(r‘\d{4}-\d{2}-\d{2}‘, ‘[DATE]‘, text) print(new_text) # 輸出Today is [DATE]. # 使用函數(shù)進(jìn)行動(dòng)態(tài)替換強(qiáng)大 def to_upper(match): return match.group().upper() text “hello world“ new_text re.sub(r‘\w‘, to_upper, text) print(new_text) # 輸出HELLO WORLD # 使用反向引用\1, \2...在替換字符串中引用分組 text “Smith, John“ new_text re.sub(r‘(\w), (\w)‘, r‘\2 \1‘, text) # 將“姓, 名”改為“名 姓” print(new_text) # 輸出John Smithre.subn()行為與sub相同但返回一個(gè)元組(新字符串, 替換次數(shù))。re.split(pattern, string, maxsplit0, flags0)用模式匹配到的部分作為分隔符來分割字符串比普通的str.split強(qiáng)大得多。# 用任意長度的非單詞字符分割 re.split(r‘\W‘, ‘Words, words, words.‘) # 返回[‘Words‘, ‘words‘, ‘words‘, ‘‘] # 保留分隔符可以在模式中使用捕獲分組 re.split(r‘(\W)‘, ‘Words, words, words.‘) # 返回[‘Words‘, ‘, ‘, ‘words‘, ‘, ‘, ‘words‘, ‘.‘, ‘‘]3.4 編譯正則表達(dá)式對(duì)象如果你需要重復(fù)使用同一個(gè)正則模式最佳實(shí)踐是使用re.compile()將其預(yù)編譯成一個(gè)正則表達(dá)式對(duì)象。這能顯著提升效率尤其是在循環(huán)或頻繁調(diào)用的場景中。pattern re.compile(r‘\d{3}-\d{3}-\d{4}‘) # 編譯一個(gè)匹配電話號(hào)碼的模式 # 之后所有方法都可以通過這個(gè)對(duì)象調(diào)用 match_result pattern.match(‘123-456-7890‘) findall_result pattern.findall(‘Call 123-456-7890 or 111-222-3333‘) sub_result pattern.sub(‘[PHONE]‘, ‘My number is 123-456-7890.‘)4. 高級(jí)技巧與性能優(yōu)化實(shí)戰(zhàn)掌握了基礎(chǔ)我們來看看如何寫出更健壯、更高效的正則表達(dá)式。4.1 標(biāo)志位Flags的妙用標(biāo)志位可以修改正則表達(dá)式引擎的某些默認(rèn)行為通過flags參數(shù)傳遞可以用|組合。re.IGNORECASE或re.I忽略大小寫。r‘python‘可以匹配‘Python‘,‘PYTHON‘。re.MULTILINE或re.M多行模式。改變^和$的行為使它們分別匹配每一行的開頭和結(jié)尾而不僅僅是整個(gè)字符串的開頭和結(jié)尾。text “first line\nsecond line\nthird line“ # 默認(rèn)模式下^匹配整個(gè)字符串開頭 re.findall(r‘^\w‘, text) # 返回[‘first‘] # 多行模式下^匹配每行開頭 re.findall(r‘^\w‘, text, flagsre.M) # 返回[‘first‘, ‘second‘, ‘third‘]re.DOTALL或re.S點(diǎn)號(hào)通配模式。讓.匹配包括換行符在內(nèi)的所有字符。這在匹配跨越多行的文本塊如HTML注釋、代碼塊時(shí)必不可少。re.VERBOSE或re.X詳細(xì)模式。允許你在正則表達(dá)式中添加空白和注釋使其更易讀。pattern re.compile(r“““ \d{3,4} # 區(qū)號(hào) -? # 可選的連接符 \d{7,8} # 電話號(hào)碼 “““, flagsre.VERBOSE)4.2 零寬斷言匹配位置不消耗字符這是正則表達(dá)式里最“魔法”的部分之一。它們用于指定一個(gè)位置這個(gè)位置需要滿足或不滿足某些條件但匹配時(shí)不占用字符。(?...)正向先行斷言匹配一個(gè)位置該位置之后的內(nèi)容需要匹配...。例Windows(?95|98|NT)匹配后面跟著95、98或NT的“Windows“但匹配結(jié)果只是“Windows“不包括后面的數(shù)字。(?!...)負(fù)向先行斷言匹配一個(gè)位置該位置之后的內(nèi)容不能匹配...。例\d{3}(?!\d)匹配一個(gè)三位數(shù)且這個(gè)三位數(shù)后面不能緊跟另一個(gè)數(shù)字。(?...)正向后行斷言匹配一個(gè)位置該位置之前的內(nèi)容需要匹配...。例(?)\w匹配符號(hào)后面的單詞常用于提取郵箱用戶名。(?!...)負(fù)向后行斷言匹配一個(gè)位置該位置之前的內(nèi)容不能匹配...。例(?!\$)\d匹配前面沒有$符號(hào)的數(shù)字。實(shí)戰(zhàn)場景提取引號(hào)內(nèi)的內(nèi)容但不包括引號(hào)本身。text ‘He said “hello“ and she said “world“.‘ # 不使用斷言會(huì)匹配到引號(hào) re.findall(r‘“.*?“‘, text) # 返回[‘“hello“‘, ‘“world“‘] # 使用后行和先行斷言只匹配內(nèi)容 re.findall(r‘(?“).*?(?“)‘, text) # 返回[‘hello‘, ‘world‘]4.3 性能陷阱與優(yōu)化策略寫得不好的正則表達(dá)式可能導(dǎo)致“災(zāi)難性回溯”讓程序陷入漫長的計(jì)算甚至卡死。主要陷阱過度使用貪婪量詞與嵌套如(.*)*這種模式在匹配失敗時(shí)回溯路徑會(huì)指數(shù)級(jí)增長。過于寬泛的字符集在長文本中用.*開頭引擎會(huì)一直嘗試匹配到字符串末尾再慢慢回溯。優(yōu)化策略具體化盡可能使用更具體的字符集代替.。用\d代替[0-9]其實(shí)更高效用[^]*代替.*?來匹配非引號(hào)內(nèi)容。避免嵌套量詞審視(a)或(.*)*這類模式思考是否真的必要。使用原子分組Atomic Group(?...)Python 3.11 支持。原子分組內(nèi)的匹配一旦完成就不會(huì)被回溯。這可以徹底切斷某些回溯路徑是解決復(fù)雜回溯問題的利器。預(yù)編譯與復(fù)用如前所述一定要預(yù)編譯重復(fù)使用的模式。適時(shí)“剎車”對(duì)于已知最大長度的匹配使用{m,n}限定范圍而不是*或。5. 綜合實(shí)戰(zhàn)案例從日志解析到數(shù)據(jù)清洗理論說再多不如看幾個(gè)真實(shí)的例子。下面這些案例都來源于我實(shí)際工作中遇到的問題。5.1 案例一解析Nginx訪問日志假設(shè)有一條 Nginx 日志格式為127.0.0.1 - - [01/Oct/2023:10:30:45 0800] “GET /api/user?id123 HTTP/1.1“ 200 1024 “-“ “Mozilla/5.0 ...“我們需要提取客戶端IP、時(shí)間、請(qǐng)求方法、URL路徑、狀態(tài)碼和響應(yīng)大小。import re log_line ‘127.0.0.1 - - [01/Oct/2023:10:30:45 0800] “GET /api/user?id123 HTTP/1.1“ 200 1024 “-“ “Mozilla/5.0 ...“‘ pattern re.compile( r‘^(?Pip\S) \S \S \[(?Ptime[^\]])\] ‘ r‘“(?Pmethod\S) (?Purl\S) (?Pprotocol\S)“ ‘ r‘(?Pstatus\d) (?Psize\d) ‘ r‘“(?Preferer[^]*)“ “(?Pagent[^]*)“$‘ ) match pattern.match(log_line) if match: data match.groupdict() print(f“IP: {data[‘ip‘]}, Method: {data[‘method‘]}, URL: {data[‘url‘]}, Status: {data[‘status‘]}“) # 進(jìn)一步解析URL中的查詢參數(shù) url_match re.search(r‘id(\d)‘, data[‘url‘]) if url_match: print(f“Extracted user id: {url_match.group(1)}“)這里使用了命名分組(?Pname...)可以讓匹配結(jié)果的引用更清晰通過.groupdict()直接得到字典。5.2 案例二清洗混亂的金融數(shù)據(jù)你從一份PDF或網(wǎng)頁上復(fù)制了一列金額數(shù)據(jù)格式混亂$1,234.56,USD 789.10,(1,000.00),500需要統(tǒng)一清洗成浮點(diǎn)數(shù)。def clean_currency(text): # 1. 移除貨幣符號(hào)、括號(hào)表示負(fù)數(shù)、逗號(hào) cleaned re.sub(r‘[$\USD\(\)]‘, ‘‘, text) # 2. 處理可能存在的表示負(fù)數(shù)的末尾括號(hào)或負(fù)號(hào) # 如果原始文本被括號(hào)包圍或包含負(fù)號(hào)則視為負(fù)數(shù) is_negative bool(re.search(r‘^\(.*\)$‘, text)) or ‘-‘ in text # 3. 移除所有非數(shù)字、非小數(shù)點(diǎn)字符再次清理 cleaned re.sub(r‘[^\d.-]‘, ‘‘, cleaned) # 4. 轉(zhuǎn)換為浮點(diǎn)數(shù) try: value float(cleaned) if cleaned else 0.0 return -value if is_negative else value except ValueError: return 0.0 amounts [‘$1,234.56‘, ‘USD 789.10‘, ‘(1,000.00)‘, ‘500‘, ‘N/A‘] cleaned [clean_currency(amt) for amt in amounts] print(cleaned) # 輸出[1234.56, 789.1, -1000.0, 500.0, 0.0]這個(gè)例子展示了正則表達(dá)式在數(shù)據(jù)清洗中的組合拳sub用于替換清理search用于條件判斷。5.3 案例三提取和重命名批量文件假設(shè)你有一堆圖片文件命名雜亂IMG_20231001_123456.jpg,photo-01-10-2023.png,2023-10-02 selfie.jpeg。你想統(tǒng)一重命名為20231001_123456.jpg這樣的格式。import os import re def rename_files_in_dir(directory): for filename in os.listdir(directory): old_path os.path.join(directory, filename) if not os.path.isfile(old_path): continue # 嘗試匹配多種日期時(shí)間模式 base, ext os.path.splitext(filename) new_base None # 模式1: IMG_YYYYMMDD_HHMMSS match re.search(r‘IMG_(\d{8})_(\d{6})‘, base, re.I) if match: new_base f“{match.group(1)}_{match.group(2)}“ # 模式2: YYYY-MM-DD 或 DD-MM-YYYY if not new_base: match re.search(r‘(\d{4})-(\d{2})-(\d{2})‘, base) if match: new_base f“{match.group(1)}{match.group(2)}{match.group(3)}“ else: match re.search(r‘(\d{2})-(\d{2})-(\d{4})‘, base) if match: new_base f“{match.group(3)}{match.group(2)}{match.group(1)}“ # 如果匹配到模式進(jìn)行重命名 if new_base: new_filename new_base ext new_path os.path.join(directory, new_filename) # 為了避免覆蓋可以加個(gè)計(jì)數(shù)器或檢查是否存在 print(f“Renaming ‘{filename}‘ - ‘{new_filename}‘“) # os.rename(old_path, new_path) # 實(shí)際重命名時(shí)取消注釋 # rename_files_in_dir(‘./your_photos‘)這個(gè)案例結(jié)合了os模塊展示了正則表達(dá)式如何用于自動(dòng)化文件管理通過多個(gè)模式嘗試匹配具有很強(qiáng)的魯棒性。6. 調(diào)試技巧、常見問題與工具推薦即使經(jīng)驗(yàn)豐富寫復(fù)雜的正則表達(dá)式也難免出錯(cuò)。掌握調(diào)試方法至關(guān)重要。6.1 調(diào)試技巧與常見“坑”從簡單開始逐步構(gòu)建不要試圖一口氣寫出完整的復(fù)雜表達(dá)式。先寫核心部分測(cè)試通過后再像搭積木一樣添加邊界條件、分組等。使用在線測(cè)試工具在編寫過程中強(qiáng)烈建議使用在線正則表達(dá)式測(cè)試器如 regex101.com。它們可以高亮顯示匹配部分、解釋每個(gè)元字符的含義、并顯示捕獲分組還能檢測(cè)性能問題。Python內(nèi)打印調(diào)試在代碼中將待匹配的字符串和你的模式都打印出來確保沒有轉(zhuǎn)義錯(cuò)誤。對(duì)于復(fù)雜模式可以分部分測(cè)試。pattern r‘\d‘ text ‘a(chǎn)bc123‘ print(f“Pattern: {pattern}“) print(f“Text: {text}“) print(re.findall(pattern, text))常見問題轉(zhuǎn)義災(zāi)難在Python字符串中反斜杠\也是轉(zhuǎn)義符。因此寫正則的\d時(shí)需要寫成r‘\d‘或‘\\d‘。強(qiáng)烈推薦使用原始字符串r‘...‘一勞永逸。貪婪 vs 非貪婪這是最常導(dǎo)致匹配結(jié)果不符合預(yù)期的原因。當(dāng)你發(fā)現(xiàn)匹配了太多內(nèi)容時(shí)第一反應(yīng)就應(yīng)該是檢查量詞后是否需要加?。多行匹配當(dāng)你使用^或$發(fā)現(xiàn)沒有按預(yù)期匹配各行時(shí)檢查是否忘記了re.MULTILINE標(biāo)志。Unicode字符默認(rèn)的\w,\d等可能不匹配非英文字符或全角數(shù)字。如果需要考慮使用Unicode屬性類如\p{L}匹配字母但Python原生re模塊不支持需使用regex第三方庫或更寬泛的字符集。6.2 當(dāng)正則力不從心時(shí)正則表達(dá)式不是萬能的。對(duì)于嵌套結(jié)構(gòu)如復(fù)雜的HTML/XML、編程語言的語法正則表達(dá)式很難甚至無法正確解析。例如用正則匹配任意深度的嵌套括號(hào)((()))是非常困難的。對(duì)于這類問題應(yīng)該使用專門的解析器如html.parser、lxml解析HTMLast模塊解析Python代碼。原則正則適合處理“平坦的”、模式規(guī)則的文本。對(duì)于具有遞歸、嵌套結(jié)構(gòu)的文本請(qǐng)選擇更合適的工具。6.3 性能監(jiān)控與第三方庫re模塊的DEBUG標(biāo)志傳入re.DEBUG標(biāo)志引擎會(huì)打印出編譯正則表達(dá)式時(shí)的調(diào)試信息解析樹對(duì)于理解復(fù)雜正則的內(nèi)部邏輯有幫助但輸出較為專業(yè)。re.compile(r‘\d{3}-\d{4}‘, flagsre.DEBUG)第三方庫regexPython官方的re模塊功能已經(jīng)很強(qiáng)但如果你需要更強(qiáng)大的功能如完整的Unicode支持、更豐富的字符屬性、可變長度的后行斷言、遞歸匹配等可以安裝regex庫pip install regex。它的API與re高度兼容但功能更強(qiáng)大。正則表達(dá)式是一門實(shí)踐性極強(qiáng)的技能。最好的學(xué)習(xí)方法就是多寫、多試、多踩坑。下次當(dāng)你面對(duì)文本處理難題時(shí)先別急著寫循環(huán)想一想“能不能用正則表達(dá)式” 很多時(shí)候一行優(yōu)雅的正則就能替代幾十行繁瑣的字符串操作代碼。把它加入你的工具箱你的編程效率會(huì)提升一個(gè)檔次。