正規表示式
1.正則是啥?
正規表示式: 定義一個搜尋模式的字串。正規表示式可以用於搜尋、編輯和操作文字.
正則對文字的分析或修改過程為: 首先正規表示式應用的是文字字串(text/string
),它會以定義的模式從左到右匹配文字,每個源字元只匹配一次.
請說人話:
正則就是用有限的符號,表達無限的序列!
說白了人就是懶,想少寫多做!
正規表示式 | 匹配 |
---|---|
this is text |
精確匹配字串 "this is text" |
this\s+is\s+text |
匹配單詞 "this" 後跟一個或多個空格字元,後跟詞 "is" 後跟一個或多個空格字元,後跟詞 "text" |
^\d+(\.\d+)? |
^ 定義模式必須匹配字串的開始,d+ 匹配一個或多個數字,? 表明小括號內的語句是可選的,\. 匹配 ".",小括號表示分組。例如匹配:"5"、"1.5" 和 "2.21" |
2.正規表示式的規則
2.1 常見匹配符號
正規表示式 | 描述 |
---|---|
. |
匹配所有單個字元,除了換行符(Linux 中換行是 n,Windows 中換行是 rn) |
^regex |
正則必須匹配字串開頭 |
regex$ |
正則必須匹配字串結尾 |
[abc] |
複選集定義,匹配字母 a 或 b 或 c |
[abc][vz] |
複選集定義,匹配字母 a 或 b 或 c,後面跟著 v 或 z |
[^abc] |
當插入符 ^ 在中括號中以第一個字元開始顯示,則表示否定模式。此模式匹配所有字元,除了 a 或 b 或 c |
[a-d1-7] |
範圍匹配,匹配字母 a 到 d 和數字從 1 到 7 之間,但不匹配 d1 |
XZ |
匹配 X 後直接跟著 Z |
X|Z |
匹配 X 或 Z |
這些符號必須要記住
2.2 元字元
元字元是一個預定義的字元。
正規表示式 | 描述 |
---|---|
\d |
匹配一個數字,是 [0-9] 的簡寫 |
\D |
匹配一個非數字,是 [^0-9] 的簡寫 |
\s |
匹配一個空格,是 [ \t\n\x0b\r\f] 的簡寫 |
\S |
匹配一個非空格 |
\w |
匹配一個單字字元(大小寫字母、數字、下劃線),是 [a-zA-Z_0-9] 的簡寫 |
\W |
匹配一個非單字字元(除了大小寫字母、數字、下劃線之外的字元),等同於 [^\w] |
2.3 限定字元
限定符定義了一個元素可以發生的頻率。
正規表示式 | 描述 | 舉例 |
* |
匹配 >=0 個,是 {0,} 的簡寫 |
X* 表示匹配零個或多個字母 X,.* 表示匹配任何字串 |
+ |
匹配 >=1 個,是 {1,} 的簡寫 |
X+ 表示匹配一個或多個字母 X |
? |
匹配 1 個或 0 個,是 {0,1} 的簡寫 |
X? 表示匹配 0 個或 1 個字母 X |
{X} |
只匹配 X 個字元 | \d{3} 表示匹配 3 個數字,.{10} 表示匹配任何長度是 10 的字串 |
{X,Y} |
匹配 >=X 且 <=Y 個 | \d{1,4} 表示匹配至少 1 個最多 4 個數字 |
*? |
如果 ? 是限定符 * 或 + 或 ? 或 {} 後面的第一個字元,那麼表示非貪婪模式(儘可能少的匹配字元),而不是預設的貪婪模式 |
2.4 分組和反向引用
小括號 ()
可以達到對正規表示式進行分組的效果。
模式分組後會在正規表示式中建立反向引用。反向引用會儲存匹配模式分組的字串片斷,這使得我們可以獲取並使用這個字串片斷。
在以正規表示式替換字串的語法中,是通過 $
來引用分組的反向引用,$0
是匹配完整模式的字串(注意在 JavaScript 中是用 $&
表示);$1
是第一個分組的反向引用;$2
是第二個分組的反向引用,以此類推。
package com.wuxianjiezh.demo.regex;
public class RegexTest {
public static void main(String[] args) {
// 去除單詞與 , 和 . 之間的空格
String Str = "Hello , World .";
String pattern = "(\\w)(\\s+)([.,])";
// $0 匹配 `(\w)(\s+)([.,])` 結果為 `o空格,` 和 `d空格.`
// $1 匹配 `(\w)` 結果為 `o` 和 `d`
// $2 匹配 `(\s+)` 結果為 `空格` 和 `空格`
// $3 匹配 `([.,])` 結果為 `,` 和 `.`
System.out.println(Str.replaceAll(pattern, "$1$3")); // Hello, World.
}
}
複製程式碼
上面的例子中,我們使用了 [.]
來匹配普通字元 .
而不需要使用 [\\.]
。因為正則對於 []
中的 .
,會自動處理為 [\.]
,即普通字元 .
進行匹配。
2.4.1 僅分組但無反向引用
當我們在小括號 ()
內的模式開頭加入 ?:
,那麼表示這個模式僅分組,但不建立反向引用。
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "img.jpg";
// 分組且建立反向引用
Pattern pattern = Pattern.compile("(jpg|png)");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());//$0
System.out.println(matcher.group(1));//$1
}
}
}
複製程式碼
執行結果為:
jpg
jpg
若原始碼改為:
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "img.jpg";
// 分組但不建立反向引用
Pattern pattern = Pattern.compile("(?:jpg|png)");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
System.out.println(matcher.group(1));
}
}
}
複製程式碼
執行結果為:
jpg Exception in thread "main" java.lang.IndexOutOfBoundsException: No group 1 at java.util.regex.Matcher.group(Matcher.java:538) at com.wuxianjiezh.regex.RegexTest.main(RegexTest.java:15)
2.4.2 分組的反向引用副本
Java 中可以在小括號中使用 ? 將小括號中匹配的內容儲存為一個名字為 name 的副本。
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "@wxj 你好啊";
Pattern pattern = Pattern.compile("@(?<first>\\w+\\s)"); // 儲存一個副本
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
System.out.println(matcher.group(1));
System.out.println(matcher.group("first"));
}
}
}
複製程式碼
執行結果為:
@wxj wxj wxj
2.5 否定先行斷言(Negative lookahead)
我們可以建立否定先行斷言模式的匹配,即某個字串後面不包含另一個字串的匹配模式。
否定先行斷言模式通過 (?!pattern)
定義。比如,我們匹配後面不是跟著 "b" 的 "a":
a(?!b)
複製程式碼
2.6 指定正規表示式的模式
可以在正則的開頭指定模式修飾符。
(?i)
使正則忽略大小寫。(?s)
表示單行模式("single line mode")使正則的.
匹配所有字元,包括換行符。(?m)
表示多行模式("multi-line mode"),使正則的^
和$
匹配字串中每行的開始和結束。
2.7 Java 中的反斜槓
反斜槓 \
在 Java 中表示轉義字元,這意味著 \
在 Java 擁有預定義的含義。
這裡例舉兩個特別重要的用法:
- 在匹配
.
或{
或[
或(
或?
或$
或^
或*
這些特殊字元時,需要在前面加上\\
,比如匹配.
時,Java 中要寫為\\.
,但對於正規表示式來說就是\.
。 - 在匹配
\
時,Java 中要寫為\\\\
,但對於正規表示式來說就是\\
。
注意:Java 中的正規表示式字串有兩層含義,首先 Java 字串轉義出符合正規表示式語法的字串,然後再由轉義後的正規表示式進行模式匹配。
2.8 易錯點示例
[jpg|png]
代表匹配j
或p
或g
或p
或n
或g
中的任意一個字元。(jpg|png)
代表匹配jpg
或png
。
3.在字串中使用正規表示式
3.1 內建的字串正則處理方法
在 Java 中有四個內建的執行正規表示式的方法,分別是 matches()
、split())
、replaceFirst()
、replaceAll()
。注意 replace()
方法不支援正規表示式。
方法 | 描述 |
---|---|
s.matches("regex") |
當僅且當正則匹配整個字串時返回 true |
s.split("regex") |
按匹配的正規表示式切片字串 |
s.replaceFirst("regex", "replacement") |
替換首次匹配的字串片段 |
s.replaceAll("regex", "replacement") |
替換所有匹配的字元 |
package com.wuxianjiezh.regex;
public class RegexTest {
public static void main(String[] args) {
System.out.println("wxj".matches("wxj"));
System.out.println("----------");
String[] array = "w x j".split("\\s");
for (String item : array) {
System.out.println(item);
}
System.out.println("----------");
System.out.println("w x j".replaceFirst("\\s", "-"));
System.out.println("----------");
System.out.println("w x j".replaceAll("\\s", "-"));
}
}
複製程式碼
執行結果
true
----------
w
x
j
----------
w-x j
----------
w-x-j
複製程式碼
4.模式和匹配
Java 中使用正規表示式需要用到兩個類,分別為 java.util.regex.Pattern
和 java.util.regex.Matcher
。
第一步,通過正規表示式建立模式物件 Pattern
。
第二步,通過模式物件 Pattern
,根據指定字串建立匹配物件 Matcher
。
第三步,通過匹配物件 Matcher
,根據正規表示式操作字串。
非常好的示例
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String text = "Hello Regex!";
Pattern pattern = Pattern.compile("\\w+");
// Java 中忽略大小寫,有兩種寫法:
// Pattern pattern = Pattern.compile("\\w+", Pattern.CASE_INSENSITIVE);
// Pattern pattern = Pattern.compile("(?i)\\w+"); // 推薦寫法
Matcher matcher = pattern.matcher(text);
// 遍例所有匹配的序列
while (matcher.find()) {
System.out.print("Start index: " + matcher.start());
System.out.print(" End index: " + matcher.end() + " ");
System.out.println(matcher.group());
}
// 建立第兩個模式,將空格替換為 tab
Pattern replace = Pattern.compile("\\s+");
Matcher matcher2 = replace.matcher(text);
System.out.println(matcher2.replaceAll("\t"));
}
}
複製程式碼
執行結果:
Start index: 0 End index: 5 Hello
Start index: 6 End index: 11 Regex
Hello Regex!
複製程式碼
5.若干個常用例子
5.1 中文的匹配
[\u4e00-\u9fa5]+
代表匹配中文字。
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "閑人到人間";
Pattern pattern = Pattern.compile("[\\u4e00-\\u9fa5]+");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
複製程式碼
執行結果:
閑人到人間
複製程式碼
5.2 數字範圍的匹配
比如,匹配 1990 到 2017。
**注意:**這裡有個新手易範的錯誤,就是正則 [1990-2017]
,實際這個正則只匹配 0
或 1
或 2
或 7
或 9
中的任一個字元。
正規表示式匹配數字範圍時,首先要確定最大值與最小值,最後寫中間值。
正確的匹配方式:
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "1990\n2010\n2017";
// 這裡應用了 (?m) 的多行匹配模式,只為方便我們測試輸出
// "^1990$|^199[1-9]$|^20[0-1][0-6]$|^2017$" 為判斷 1990-2017 正確的正規表示式
Pattern pattern = Pattern.compile("(?m)^1990$|^199[1-9]$|^20[0-1][0-6]$|^2017$");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group());
}
}
}
複製程式碼
執行結果:
1990
2010
2017
複製程式碼
5.3 img 標籤的匹配
比如,獲取圖片檔案內容,這裡我們考慮了一些不規範的 img 標籤寫法:
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "<img src='aaa.jpg' /><img src=bbb.png/><img src=\"ccc.png\"/>" +
"<img src='ddd.exe'/><img src='eee.jpn'/>";
// 這裡我們考慮了一些不規範的 img 標籤寫法,比如:空格、引號
Pattern pattern = Pattern.compile("<img\\s+src=(?:['\"])?(?<src>\\w+.(jpg|png))(?:['\"])?\\s*/>");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group("src"));
}
}
}
複製程式碼
執行結果:
aaa.jpg
bbb.png
ccc.png
複製程式碼
5.4 貪婪與非貪婪模式的匹配
比如,獲取 div 標籤中的文字內容:
package com.wuxianjiezh.regex;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class RegexTest {
public static void main(String[] args) {
String str = "<div>文章標題</div><div>釋出時間</div>";
// 貪婪模式
Pattern pattern = Pattern.compile("<div>(?<title>.+)</div>");
Matcher matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group("title"));
}
System.out.println("--------------");
// 非貪婪模式
pattern = Pattern.compile("<div>(?<title>.+?)</div>");
matcher = pattern.matcher(str);
while (matcher.find()) {
System.out.println(matcher.group("title"));
}
}
}
複製程式碼
執行結果:
文章標題</div><div>釋出時間
--------------
文章標題
釋出時間
複製程式碼
6.推薦詳細的自學教程
正規表示式30分鐘入門教程 https://deerchao.net/tutorials/regex/regex.htm
Java的正規表示式工具 http://www.regexplanet.com/advanced/java/index.html
正規表示式語法篇 https://yanhaijing.com/javascript/2017/08/06/regexp-syntax/
正規表示式語法詳解篇 https://blog.csdn.net/yaerfeng/article/details/28855587#reg
JavaScript正規表示式 https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions
7.附錄
問題1:關於空格
針對tab鍵帶來的多個空格問題,有時候我們針對帶空格的一行資料要進行切割,如果有多個空格就會出現就會切割空格出現,我們想把空格都去掉,所以需要用到某些方法。
解決方案: 利用正規表示式來匹配空格\\s+
首先利用split(“\s+”);方法來對字串切割,儘可能的匹配空格,這裡也挺有意思,因為空格數目不一樣,可以動態變換匹配的空格數量,這個實現原理可以看看底層原理,挺有意思。
String string="a b a a ";
for(String a:string.split("\\s+")){
System.out.println(a);
}
複製程式碼
問題2:[] {} () 的使用區別
()
是為了提取匹配的字串。表示式中有幾個()
就有幾個相應的匹配字串。(\s*)
表示連續空格的字串。[]
是定義匹配的字元範圍。比如[a-zA-Z0-9]
表示相應位置的字元要匹配英文字元和數字。[\s*]
表示空格或者*號。{}
一般用來表示匹配的長度,比如\s{3}
表示匹配三個空格,\s{1,3}
表示匹配一到三個空格。(0-9)
匹配 '0-9′ 本身。[0-9]*
匹配數字(注意後面有*
,可以為空)[0-9]+
匹配數字(注意後面有 +,不可以為空){1-9}
寫法錯誤。[0-9]{0,9}
表示長度為 0 到 9 的數字字串
問題3: ()和[]有本質的區別
()
內的內容表示的是一個子表示式,()本身不匹配任何東西,也不限制匹配任何東西,只是把括號內的內容作為同一個表示式來處理!
例如:(ab){1,3},就表示ab一起連續出現最少1次,最多3次。如果沒有括號的話,ab{1,3},就表示a,後面緊跟的b出現最少1次,最多3次。另外,括號在匹配模式中也很重要。檢視前面的介紹 []表示匹配的字元在[]中,並且只能出現一次,並且特殊字元寫在[]會被當成普通字元來匹配。例如[(a)],會匹配(、a、)、這三個字元。 所以() [] 無論是作用還是表示的含義,都有天壤之別,沒什麼聯絡