Java 正則表達(dá)式詳解

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] 代表匹配 jpgpng 中的任意一個(gè)字符靴拱。
  • (jpg|png) 代表匹配 jpgpng

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.Patternjava.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è)正則只匹配 01279 中的任一個(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è)在線正則工具

7. 參考

Java Regex - Tutorial

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市屯耸,隨后出現(xiàn)的幾起案子拐迁,更是在濱河造成了極大的恐慌,老刑警劉巖疗绣,帶你破解...
    沈念sama閱讀 212,383評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件线召,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡多矮,警方通過查閱死者的電腦和手機(jī)缓淹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門哈打,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人讯壶,你說我怎么就攤上這事料仗。” “怎么了伏蚊?”我有些...
    開封第一講書人閱讀 157,852評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵立轧,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我躏吊,道長(zhǎng)氛改,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,621評(píng)論 1 284
  • 正文 為了忘掉前任比伏,我火速辦了婚禮胜卤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘赁项。我一直安慰自己葛躏,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,741評(píng)論 6 386
  • 文/花漫 我一把揭開白布肤舞。 她就那樣靜靜地躺著紫新,像睡著了一般。 火紅的嫁衣襯著肌膚如雪李剖。 梳的紋絲不亂的頭發(fā)上芒率,一...
    開封第一講書人閱讀 49,929評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音篙顺,去河邊找鬼偶芍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛德玫,可吹牛的內(nèi)容都是我干的匪蟀。 我是一名探鬼主播,決...
    沈念sama閱讀 39,076評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼宰僧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼材彪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起琴儿,我...
    開封第一講書人閱讀 37,803評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤段化,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后造成,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體显熏,經(jīng)...
    沈念sama閱讀 44,265評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,582評(píng)論 2 327
  • 正文 我和宋清朗相戀三年晒屎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了喘蟆。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片缓升。...
    茶點(diǎn)故事閱讀 38,716評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蕴轨,靈堂內(nèi)的尸體忽然破棺而出港谊,到底是詐尸還是另有隱情,我是刑警寧澤尺棋,帶...
    沈念sama閱讀 34,395評(píng)論 4 333
  • 正文 年R本政府宣布封锉,位于F島的核電站,受9級(jí)特大地震影響膘螟,放射性物質(zhì)發(fā)生泄漏成福。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,039評(píng)論 3 316
  • 文/蒙蒙 一荆残、第九天 我趴在偏房一處隱蔽的房頂上張望奴艾。 院中可真熱鬧,春花似錦内斯、人聲如沸蕴潦。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽潭苞。三九已至,卻和暖如春真朗,著一層夾襖步出監(jiān)牢的瞬間此疹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評(píng)論 1 266
  • 我被黑心中介騙來泰國(guó)打工遮婶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蝗碎,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,488評(píng)論 2 361
  • 正文 我出身青樓旗扑,卻偏偏與公主長(zhǎng)得像蹦骑,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子臀防,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,612評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容