導讀
正規表達式是什麼?有什麼用?
正規表達式(Regular Expression)是一種文本規則,可以用來校驗、查找、替換與規則比對的文本。
又愛又恨的正則
正規表達式是一個強大的文本比對工具,但是它的規則實在很繁瑣,而且了解起來也頗為蛋疼,容易讓人望而生畏。
如何學習正則
剛接觸正則時,我看了一堆正則的語義說明,但是仍然不明是以。後來,我多接觸一些正則的應用執行個體,漸漸有了感覺,再結合語義說明,終有領悟。我覺得正規表達式和武俠修練武功差不多,應該先練招式,再練心法。如果一開始就直接看正則的規則,保證你會懵逼。
當你熟悉基本招式(正則基本使用案例)後,也該修煉修煉心法(正則文法)了。真正的高手不能隻靠死記硬背那麼幾招把式。就像張三豐教張無忌太極拳一樣,領悟心法,融會貫通,少俠你就可以無招勝有招,成為傳說中的絕世高手。
以上閑話可歸納為一句:學習正則應該從執行個體去了解規則。

打開秘籍:欲練神功,必先自宮!沒有蛋,也就不會蛋疼了。
Java正則速成秘籍分三篇:
展示Java對于正規表達式的支援。
介紹正規表達式的文法規則。
從實戰出發,介紹正則的常用案例。
本文是Java正則速成秘籍的招式篇。主要介紹JDK對于正規表達式的支援。
概述
JDK中的
java.util.regex
包提供了對正規表達式的支援。
java.util.regex
有三個核心類:
- Pattern類:
是一個正規表達式的編譯表示。Pattern
- Matcher類:
是對輸入字元串進行解釋和比對操作的引擎。Matcher
- PatternSyntaxException:
是一個非強制異常類,它表示一個正規表達式模式中的文法錯誤。PatternSyntaxException
注:需要格外注意一點,在Java中使用反斜杠"\"時必須寫成
"\\"
。是以本文的代碼出現形如
String regex = "\\$\\{.*?\\}"
其實就是"\$\{.*?\}",不要以為是畫風不對哦。
Pattern類
Pattern
類沒有公共構造方法。要建立一個
Pattern
對象,你必須首先調用其靜态方法
compile
,加載正則規則字元串,然後傳回一個Pattern對象。
與
Pattern
類一樣,
Matcher
類也沒有公共構造方法。你需要調用
Pattern
對象的
matcher
方法來獲得一個
Matcher
對象。
案例:Pattern和Matcher的初始化
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
Matcher類
Matcher
類可以說是
java.util.regex
核心類中的必殺技!
Matcher
類有三闆斧(三類功能):
- 校驗
- 查找
- 替換
下面我們來領略一下這三塊的功能。
校驗文本是否與正則規則比對
為了檢查文本是否與正則規則比對,Matcher提供了以下幾個傳回值為
boolean
的方法。
序号 | 方法及說明 |
---|---|
1 | public boolean lookingAt() 嘗試将從區域開頭開始的輸入序列與該模式比對。 |
2 | public boolean find() 嘗試查找與該模式比對的輸入序列的下一個子序列。 |
3 | public boolean find(int start)重置此比對器,然後嘗試查找比對該模式、從指定索引開始的輸入序列的下一個子序列。 |
4 | public boolean matches() 嘗試将整個區域與模式比對。 |
如果你傻傻分不清上面的查找方法有什麼差別,那麼下面一個例子就可以讓你秒懂。
案例:lookingAt vs find vs matches
public static void main(String[] args) {
checkLookingAt("hello", "helloworld");
checkLookingAt("world", "helloworld");
checkFind("hello", "helloworld");
checkFind("world", "helloworld");
checkMatches("hello", "helloworld");
checkMatches("world", "helloworld");
checkMatches("helloworld", "helloworld");
}
private static void checkLookingAt(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.lookingAt()) {
System.out.println(content + "\tlookingAt: " + regex);
} else {
System.out.println(content + "\tnot lookingAt: " + regex);
}
}
private static void checkFind(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.find()) {
System.out.println(content + "\tfind: " + regex);
} else {
System.out.println(content + "\tnot find: " + regex);
}
}
private static void checkMatches(String regex, String content) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
if (m.matches()) {
System.out.println(content + "\tmatches: " + regex);
} else {
System.out.println(content + "\tnot matches: " + regex);
}
}
輸出
helloworld lookingAt: hello
helloworld not lookingAt: world
helloworld find: hello
helloworld find: world
helloworld not matches: hello
helloworld not matches: world
helloworld matches: helloworld
說明
regex = “world” 表示的正則規則是以world開頭的字元串,regex = “hello” 和regex = “helloworld” 也是同理。
-
方法從頭部開始,檢查content字元串是否有子字元串于正則規則比對。lookingAt
-
方法檢查content字元串是否有子字元串于正則規則比對,不管字元串所在位置。find
-
方法檢查content字元串整體是否與正則規則比對。matches
查找比對正則規則的文本位置
為了查找文本比對正則規則的位置,
Matcher
提供了以下方法:
public int start() 傳回以前比對的初始索引。 | |
public int start(int group) 傳回在以前的比對操作期間,由給定組所捕獲的子序列的初始索引 | |
public int end()傳回最後比對字元之後的偏移量。 | |
public int end(int group)傳回在以前的比對操作期間,由給定組所捕獲子序列的最後字元之後的偏移量。 | |
5 | public String group()傳回前一個符合比對條件的子序列。 |
6 | public String group(int group)傳回指定的符合比對條件的子序列。 |
案例:使用start()、end()、group() 查找所有比對正則條件的子序列
public static void main(String[] args) {
final String regex = "world";
final String content = "helloworld helloworld";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
System.out.println("content: " + content);
int i = 0;
while (m.find()) {
i++;
System.out.println("[" + i + "th] found");
System.out.print("start: " + m.start() + ", ");
System.out.print("end: " + m.end() + ", ");
System.out.print("group: " + m.group() + "\n");
}
}
content: helloworld helloworld
[1th] found
start: 5, end: 10, group: world
[2th] found
start: 16, end: 21, group: world
例子很直白,不言自明了吧。
替換比對正則規則的文本
替換方法是替換輸入字元串裡文本的方法:
public Matcher appendReplacement(StringBuffer sb, String replacement)實作非終端添加和替換步驟。 | |
public StringBuffer appendTail(StringBuffer sb)實作終端添加和替換步驟。 | |
public String replaceAll(String replacement) 替換模式與給定替換字元串相比對的輸入序列的每個子序列。 | |
public String replaceFirst(String replacement) 替換模式與給定替換字元串比對的輸入序列的第一個子序列。 | |
public static String quoteReplacement(String s)傳回指定字元串的字面替換字元串。這個方法傳回一個字元串,就像傳遞給Matcher類的appendReplacement 方法一個字面字元串一樣工作。 |
案例:replaceFirst vs replaceAll
public static void main(String[] args) {
String regex = "can";
String replace = "can not";
String content = "I can because I think I can.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
System.out.println("content: " + content);
System.out.println("replaceFirst: " + m.replaceFirst(replace));
System.out.println("replaceAll: " + m.replaceAll(replace));
}
content: I can because I think I can.
replaceFirst: I can not because I think I can.
replaceAll: I can not because I think I can not.
replaceFirst:替換第一個比對正則規則的子序列。
replaceAll:替換所有比對正則規則的子序列。
案例:appendReplacement、appendTail和replaceAll
public static void main(String[] args) {
String regex = "can";
String replace = "can not";
String content = "I can because I think I can.";
StringBuffer sb = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
System.out.println("content: " + content);
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
while (m.find()) {
m.appendReplacement(sb, replace);
}
System.out.println("appendReplacement: " + sb);
m.appendTail(sb);
System.out.println("appendTail: " + sb);
}
content: I can because I think I can.
appendReplacement: I can not because I think I can not
appendTail: I can not because I think I can not.
從輸出結果可以看出,
appendReplacement
和
appendTail
方法組合起來用,功能和
replaceAll
是一樣的。
如果你檢視
replaceAll
的源碼,會發現其内部就是使用
appendReplacement
appendTail
方法組合來實作的。
案例:quoteReplacement和replaceAll,解決特殊字元替換問題
public static void main(String[] args) {
String regex = "\\$\\{.*?\\}";
String replace = "${product}";
String content = "product is ${productName}.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
String replaceAll = m.replaceAll(replace);
System.out.println("content: " + content);
System.out.println("replaceAll: " + replaceAll);
}
Exception in thread "main" java.lang.IllegalArgumentException: No group with name {product}
at java.util.regex.Matcher.appendReplacement(Matcher.java:849)
at java.util.regex.Matcher.replaceAll(Matcher.java:955)
at org.zp.notes.javase.regex.RegexDemo.wrongMethod(RegexDemo.java:42)
at org.zp.notes.javase.regex.RegexDemo.main(RegexDemo.java:18)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147)
String regex = "\\$\\{.*?\\}";
表示比對類似
${name}
這樣的字元串。由于
$
、
{
}
都是特殊字元,需要用反義字元
\
來修飾才能被當做一個字元串字元來處理。
上面的例子是想将
${productName}
替換為
${product}
,然而
replaceAll
方法卻将傳入的字元串中的
$
當做特殊字元來處理了。結果産生異常。
如何解決這個問題?
JDK1.5引入了
quoteReplacement
方法。它可以用來轉換特殊字元。其實源碼非常簡單,就是判斷字元串中如果有
\
或
$
,就為它加一個轉義字元
\
我們對上面的代碼略作調整:
m.replaceAll(replace)
改為
m.replaceAll(Matcher.quoteReplacement(replace))
,新代碼如下:
public static void main(String[] args) {
String regex = "\\$\\{.*?\\}";
String replace = "${product}";
String content = "product is ${productName}.";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(content);
String replaceAll = m.replaceAll(Matcher.quoteReplacement(replace));
System.out.println("content: " + content);
System.out.println("replaceAll: " + replaceAll);
}
content: product is ${productName}.
replaceAll: product is ${product}.
字元串中如果有
\
$
,不能被正常解析的問題解決。