1. 正則表達(dá)式
1.1 什么是正則表達(dá)式
正則表達(dá)式
: 定義一個(gè)搜索模式的字符串惶洲。
正則表達(dá)式可以用于搜索衬横、編輯和操作文本裹粤。
正則對(duì)文本的分析或修改過程為:首先正則表達(dá)式應(yīng)用的是文本字符串(text/string),它會(huì)以定義的模式從左到右匹配文本冕香,每個(gè)源字符只匹配一次蛹尝。
1.2 示例
正則表達(dá)式
匹配
this is text
精確匹配字符串 "this is text"
this\s+is\s+text
匹配單詞 "this" 后跟一個(gè)或多個(gè)空格字符,后跟詞 "is" 后跟一個(gè)或多個(gè)空格字符悉尾,后跟詞 "text"
^\d+(\.\d+)?
^
定義模式必須匹配字符串的開始突那,d+
匹配一個(gè)或多個(gè)數(shù)字,?
表明小括號(hào)內(nèi)的語句是可選的构眯,\.
匹配 "."愕难,小括號(hào)表示分組。例如匹配:"5"、"1.5" 和 "2.21"
2. 正則表達(dá)式的編寫規(guī)則
2.1 常見匹配符號(hào)
正則表達(dá)式
描述
.
匹配所有單個(gè)字符猫缭,除了換行符(Linux 中換行是 \n
葱弟,Windows 中換行是 \r\n
)
^regex
正則必須匹配字符串開頭
regex$
正則必須匹配字符串結(jié)尾
[abc]
復(fù)選集定義,匹配字母 a 或 b 或 c
[abc][vz]
復(fù)選集定義猜丹,匹配字母 a 或 b 或 c芝加,后面跟著 v 或 z
[^abc]
當(dāng)插入符 ^
在中括號(hào)中以第一個(gè)字符開始顯示,則表示否定模式射窒。此模式匹配所有字符藏杖,除了 a 或 b 或 c
[a-d1-7]
范圍匹配,匹配字母 a 到 d 和數(shù)字從 1 到 7 之間脉顿,但不匹配 d1
XZ
匹配 X 后直接跟著 Z
X|Z
匹配 X 或 Z
2.2 元字符
元字符是一個(gè)預(yù)定義的字符蝌麸。
正則表達(dá)式
描述
\d
匹配一個(gè)數(shù)字,是 [0-9]
的簡(jiǎn)寫
\D
匹配一個(gè)非數(shù)字艾疟,是 [^0-9]
的簡(jiǎn)寫
\s
匹配一個(gè)空格来吩,是 [ \t\n\x0b\r\f]
的簡(jiǎn)寫
\S
匹配一個(gè)非空格
\w
匹配一個(gè)單詞字符(大小寫字母、數(shù)字蔽莱、下劃線)弟疆,是 [a-zA-Z_0-9]
的簡(jiǎn)寫
\W
匹配一個(gè)非單詞字符(除了大小寫字母、數(shù)字碾褂、下劃線之外的字符)兽间,等同于 [^\w]
2.3 限定符
限定符定義了一個(gè)元素可以發(fā)生的頻率。
正則表達(dá)式
描述
舉例
*
匹配 >=0 個(gè)正塌,是 {0,}
的簡(jiǎn)寫
X*
表示匹配零個(gè)或多個(gè)字母 X,.*
表示匹配任何字符串
+
匹配 >=1 個(gè)恤溶,是 {1,}
的簡(jiǎn)寫
X+
表示匹配一個(gè)或多個(gè)字母 X
?
匹配 1 個(gè)或 0 個(gè)乓诽,是 {0,1}
的簡(jiǎn)寫
X?
表示匹配 0 個(gè)或 1 個(gè)字母 X
{X}
只匹配 X 個(gè)字符
\d{3}
表示匹配 3 個(gè)數(shù)字,.{10}
表示匹配任何長(zhǎng)度是 10 的字符串
{X,Y}
匹配 >=X 且 <=Y 個(gè)
\d{1,4}
表示匹配至少 1 個(gè)最多 4 個(gè)數(shù)字
*?
如果 ?
是限定符 *
或 +
或 ?
或 {}
后面的第一個(gè)字符咒程,那么表示非貪婪模式(盡可能少的匹配字符)鸠天,而不是默認(rèn)的貪婪模式
2.4 分組和反向引用
小括號(hào) ()
可以達(dá)到對(duì)正則表達(dá)式進(jìn)行分組的效果。
模式分組后會(huì)在正則表達(dá)式中創(chuàng)建反向引用帐姻。反向引用會(huì)保存匹配模式分組的字符串片斷稠集,這使得我們可以獲取并使用這個(gè)字符串片斷。
在以正則表達(dá)式替換字符串的語法中饥瓷,是通過 $
來引用分組的反向引用剥纷,$0
是匹配完整模式的字符串(注意在 JavaScript 中是用 $&
表示);$1
是第一個(gè)分組的反向引用呢铆;$2
是第二個(gè)分組的反向引用晦鞋,以此類推。
示例:
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+)([.,])` 結(jié)果為 `o空格,` 和 `d空格.`
// $1 匹配 `(\w)` 結(jié)果為 `o` 和 `d`
// $2 匹配 `(\s+)` 結(jié)果為 `空格` 和 `空格`
// $3 匹配 `([.,])` 結(jié)果為 `,` 和 `.`
System.out.println(Str.replaceAll(pattern, "$1$3")); // Hello, World.
}
}
上面的例子中,我們使用了 [.]
來匹配普通字符 .
而不需要使用 [\\.]
悠垛。因?yàn)檎齽t對(duì)于 []
中的 .
线定,會(huì)自動(dòng)處理為 [\.]
,即普通字符 .
進(jìn)行匹配确买。
2.4.1 僅分組但無反向引用
當(dāng)我們?cè)谛±ㄌ?hào) ()
內(nèi)的模式開頭加入 ?:
斤讥,那么表示這個(gè)模式僅分組,但不創(chuàng)建反向引用湾趾。
示例:
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";
// 分組且創(chuàng)建反向引用
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));
}
}
}
運(yùn)行結(jié)果:
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";
// 分組但不創(chuàng)建反向引用
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));
}
}
}
運(yùn)行結(jié)果:
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 中可以在小括號(hào)中使用 ?<name>
將小括號(hào)中匹配的內(nèi)容保存為一個(gè)名字為 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)"); // 保存一個(gè)副本
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"));
}
}
}
運(yùn)行結(jié)果:
@wxj
wxj
wxj
2.5 否定先行斷言(Negative lookahead)
我們可以創(chuàng)建否定先行斷言模式的匹配,即某個(gè)字符串后面不包含另一個(gè)字符串的匹配模式撑帖。
否定先行斷言模式通過 (?!pattern)
定義蓉坎。比如,我們匹配后面不是跟著 "b" 的 "a":
a(?!b)
2.6 指定正則表達(dá)式的模式
可以在正則的開頭指定模式修飾符胡嘿。
-
(?i)
使正則忽略大小寫蛉艾。 -
(?s)
表示單行模式("single line mode")使正則的.
匹配所有字符,包括換行符衷敌。 -
(?m)
表示多行模式("multi-line mode")勿侯,使正則的^
和$
匹配字符串中每行的開始和結(jié)束。
2.7 Java 中的反斜杠
反斜杠 \
在 Java 中表示轉(zhuǎn)義字符缴罗,這意味著 \
在 Java 擁有預(yù)定義的含義助琐。
這里例舉兩個(gè)特別重要的用法:
- 在匹配
.
或{
或[
或(
或?
或$
或^
或*
這些特殊字符時(shí),需要在前面加上\\
面氓,比如匹配.
時(shí)兵钮,Java 中要寫為\\.
,但對(duì)于正則表達(dá)式來說就是\.
舌界。 - 在匹配
\
時(shí)掘譬,Java 中要寫為\\\\
,但對(duì)于正則表達(dá)式來說就是\\
呻拌。
注意:Java 中的正則表達(dá)式字符串有兩層含義葱轩,首先 Java 字符串轉(zhuǎn)義出符合正則表達(dá)式語法的字符串,然后再由轉(zhuǎn)義后的正則表達(dá)式進(jìn)行模式匹配藐握。
2.8 易錯(cuò)點(diǎn)示例
-
[jpg|png]
代表匹配j
或p
或g
或p
或n
或g
中的任意一個(gè)字符靴拱。 -
(jpg|png)
代表匹配jpg
或png
。
3. 在字符串中使用正則表達(dá)式
3.1 內(nèi)置的字符串正則處理方法
在 Java 中有四個(gè)內(nèi)置的運(yùn)行正則表達(dá)式的方法猾普,分別是 matches()
袜炕、split())
、replaceFirst()
抬闷、replaceAll()
妇蛀。注意 replace()
方法不支持正則表達(dá)式耕突。
方法
描述
s.matches("regex")
當(dāng)僅且當(dāng)正則匹配整個(gè)字符串時(shí)返回 true
s.split("regex")
按匹配的正則表達(dá)式切片字符串
s.replaceFirst("regex", "replacement")
替換首次匹配的字符串片段
s.replaceAll("regex", "replacement")
替換所有匹配的字符
3.2 示例
示例代碼:
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", "-"));
}
}
運(yùn)行結(jié)果:
true
----------
w
x
j
----------
w-x j
----------
w-x-j
4. 模式和匹配
Java 中使用正則表達(dá)式需要用到兩個(gè)類,分別為 java.util.regex.Pattern
和 java.util.regex.Matcher
评架。
第一步眷茁,通過正則表達(dá)式創(chuàng)建模式對(duì)象 Pattern
。
第二步纵诞,通過模式對(duì)象 Pattern
上祈,根據(jù)指定字符串創(chuàng)建匹配對(duì)象 Matcher
。
第三步浙芙,通過匹配對(duì)象 Matcher
登刺,根據(jù)正則表達(dá)式操作字符串。
來個(gè)例子嗡呼,加深理解:
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());
}
// 創(chuàng)建第兩個(gè)模式,將空格替換為 tab
Pattern replace = Pattern.compile("\\s+");
Matcher matcher2 = replace.matcher(text);
System.out.println(matcher2.replaceAll("\t"));
}
}
運(yùn)行結(jié)果:
Start index: 0 End index: 5 Hello
Start index: 6 End index: 11 Regex
Hello Regex!
5. 若干個(gè)常用例子
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());
}
}
}
運(yùn)行結(jié)果:
閑人到人間
5.2 數(shù)字范圍的匹配
比如揍很,匹配 1990 到 2017。
注意:這里有個(gè)新手易范的錯(cuò)誤万伤,就是正則 [1990-2017]
窒悔,實(shí)際這個(gè)正則只匹配 0
或 1
或 2
或 7
或 9
中的任一個(gè)字符。
正則表達(dá)式匹配數(shù)字范圍時(shí)敌买,首先要確定最大值與最小值简珠,最后寫中間值。
正確的匹配方式:
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";
// 這里應(yīng)用了 (?m) 的多行匹配模式虹钮,只為方便我們測(cè)試輸出
// "^1990$|^199[1-9]$|^20[0-1][0-6]$|^2017$" 為判斷 1990-2017 正確的正則表達(dá)式
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());
}
}
}
運(yùn)行結(jié)果:
1990
2010
2017
5.3 img 標(biāo)簽的匹配
比如聋庵,獲取圖片文件內(nèi)容,這里我們考慮了一些不規(guī)范的 img 標(biāo)簽寫法:
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'/>";
// 這里我們考慮了一些不規(guī)范的 img 標(biāo)簽寫法芜抒,比如:空格珍策、引號(hào)
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"));
}
}
}
運(yùn)行結(jié)果:
aaa.jpg
bbb.png
ccc.png
5.4 貪婪與非貪婪模式的匹配
比如,獲取 div 標(biāo)簽中的文本內(nèi)容:
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>文章標(biāo)題</div><div>發(fā)布時(shí)間</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"));
}
}
}
運(yùn)行結(jié)果:
文章標(biāo)題</div><div>發(fā)布時(shí)間
--------------
文章標(biāo)題
發(fā)布時(shí)間
6. 推薦兩個(gè)在線正則工具
- JavaScript宅倒、Python 等的在線表達(dá)式工具:https://regex101.com/
- Java 在線表達(dá)式工具:http://www.regexplanet.com/advanced/java/index.html