正则表达式小记

正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。现阶段很多语言有正则的实现,比如java,python等等。简单的关于字符串的替换查找等的操作可以通过字符串的API来操作,但是有些情况,通过API要进行很多额外的操作才能完成的功能,通过正则一个简单的表达式就可以轻松搞定,不信请往下看。

基础语法

一. 表示位置

1. ^ 匹配输入字符串的开始位置。$ 匹配输入字符串的结束位置

@Test
public void test15() {
    Pattern pattern = Pattern.compile("^a.*b$");
    Matcher matcher1 = pattern.matcher("adb");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("db");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("ad");
    System.out.println(matcher3.find());  //true
}
} 

二. 表示次数

3. * 匹配前面的子表达式零次或多次,相当于{0,}

@Test
public void test16(){
    Pattern pattern = Pattern.compile("a*");
    Matcher matcher1 = pattern.matcher("");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("a");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("aa");
    System.out.println(matcher3.find());  //true
}

4. + 匹配前面的子表达式一次或多次,相当于{1,}

@Test
public void test17(){
    Pattern pattern = Pattern.compile("a+");
    Matcher matcher1 = pattern.matcher("");
    System.out.println(matcher1.find());  //false

    Matcher matcher2 = pattern.matcher("a");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("aa");
    System.out.println(matcher3.find());  //true
}

5. ? 匹配前面的子表达式零次或一次,相当于{0,1}

@Test
public void test18(){
    Pattern pattern = Pattern.compile("do(es)?");
    Matcher matcher1 = pattern.matcher("do");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("does");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("doe");
    System.out.println(matcher3.find());  //true
}

0次或者1次,那么也就是do或者does,所以包含do或者does的都会被选出来,doe也包含do,所以也查找到了。

6. {n} n是一个非负整数。匹配确定的n次

@Test
public void test19(){
    Pattern pattern = Pattern.compile("n{2}");
    Matcher matcher1 = pattern.matcher("abnn");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("abn");
    System.out.println(matcher2.find());  //false

    Matcher matcher3 = pattern.matcher("ab");
    System.out.println(matcher3.find());  //false
}

7. {n,} n是一个非负整数。至少匹配n次

@Test
public void test20(){
    Pattern pattern = Pattern.compile("n{2,}");
    Matcher matcher1 = pattern.matcher("abnnn");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("abnn");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("abn");
    System.out.println(matcher3.find());  //false
}

8. {n,m} m和n均为非负整数,其中n<=m。最少匹配n次且最多匹配m次

@Test
public void test21() {
    System.out.println("abnnnn".replaceAll("n{2,3}", ""));  //abn 替换了前面3个n为空的字符串
    System.out.println("abnnn".replaceAll("n{2,3}", ""));  //ab 替换了3个n为空的字符串
    System.out.println("abnn".replaceAll("n{2,3}", ""));  //ab 替换了2个n为空的字符串
    System.out.println("abn".replaceAll("n{2,3}", ""));  //abn 未匹配到,不做替换,还是原来字符创
}

9. 特殊的?:当该字符紧跟在任何一个其他限制符 (*, +, ?, {n}, {n,}, {n,m}) 后面时,匹配模式是非贪婪的。非贪婪模式尽可能少的匹配所搜索的字符串,而默认的贪婪模式则尽可能多的匹配所搜索的字符串

@Test
public void test22() {
    Pattern pattern = Pattern.compile("n{2,3}?"); //贪婪模式
    Matcher matcher1 = pattern.matcher("abnnnnn");
    while (matcher1.find()){
       System.out.println(matcher1.group());
    }
}

结果:nn输出两遍。因为是懒惰模式,所以会找最小满足要求的,也就是连续的两个n,因为我们现在有连续5个n,所以可以找到2次

三.表示功能(实在找不到别的词了)

10. · 匹配除换行符(\n)与回车符(\r)之外的任何单个字符。

    @Test
public void test23(){
    Pattern pattern = Pattern.compile(".");
    Matcher matcher1 = pattern.matcher("a");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("3");
    System.out.println(matcher2.find());  //true


    Matcher matcher3 = pattern.matcher("\t");
    System.out.println(matcher3.find());  //true

    Matcher matcher4 = pattern.matcher("\r");
    System.out.println(matcher4.find());  //false

    Matcher matcher5 = pattern.matcher("\n");
    System.out.println(matcher5.find());  //false


}

11. \d 匹配一个数字字符,等价于 [0-9]。\D 匹配一个非数字字符。等价于 [^0-9]

@Test
public void test24(){
    Pattern pattern = Pattern.compile("^\\d+$");
    Matcher matcher1 = pattern.matcher("23");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("3d");
    System.out.println(matcher2.find());  //false


    Pattern pattern2 = Pattern.compile("^\\D+$");
    Matcher matcher3 = pattern2.matcher("23");
    System.out.println(matcher3.find());  //false

    Matcher matcher4 = pattern2.matcher("3d");
    System.out.println(matcher4.find());  //false

    Matcher matcher5 = pattern2.matcher("dd");
    System.out.println(matcher5.find());  //true
    
}

12. \w 匹配字母、数字、下划线,等价于'[A-Za-z0-9_]'。\W匹配非字母、数字、下划线,等价于 '[^A-Za-z0-9_]'。

    @Test
public void test25(){
    Pattern pattern = Pattern.compile("^\\w+$");
    Matcher matcher1 = pattern.matcher("3Dd_");
    System.out.println(matcher1.find());  //true
    
    Pattern pattern2 = Pattern.compile("^\\W+$");
    Matcher matcher3 = pattern2.matcher("\t\n\r\b");
    System.out.println(matcher3.find());  //true

}

13. \s 匹配任何空白字符,包括空格、制表符、换页符等等。\S匹配任何非空白字符。

@Test
public void test26(){
    Pattern pattern = Pattern.compile("^\\s+$");
    Matcher matcher1 = pattern.matcher(" \t\r\n");
    System.out.println(matcher1.find());  //true

    Pattern pattern2 = Pattern.compile("^\\S+$");
    Matcher matcher3 = pattern2.matcher("aA3_");
    System.out.println(matcher3.find());  //true

}

四. 范围中匹配一个

14. x|y 匹配x或y。

@Test
public void test27(){
    Pattern pattern = Pattern.compile("a|b");
    Matcher matcher1 = pattern.matcher("ac");
    System.out.println(matcher1.find());  //true

    Matcher matcher2 = pattern.matcher("bc");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("cc");
    System.out.println(matcher3.find());  //false


}

15. [xyz] 匹配所包含的任意一个字符。[^xyz]匹配未包含的任意字符。

@Test
public void test29(){
    Pattern pattern = Pattern.compile("^[abc]+$");
    Matcher matcher1 = pattern.matcher("aA");
    System.out.println(matcher1.find());  //false

    Matcher matcher2 = pattern.matcher("bb");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("CC");
    System.out.println(matcher3.find());  //false

    Pattern pattern2 = Pattern.compile("^[^abc]+$");
    Matcher matcher4 = pattern2.matcher("aA");
    System.out.println(matcher4.find());  //false

    Matcher matcher5 = pattern2.matcher("bb");
    System.out.println(matcher5.find());  //false

    Matcher matcher6 = pattern2.matcher("CC");
    System.out.println(matcher6.find());  //true

}

16. [a-z] 匹配指定范围内的任意字符。[^a-z]匹配任何不在指定范围内的任意字符。

 @Test
public void test28(){
    Pattern pattern = Pattern.compile("^[a-z]+$");
    Matcher matcher1 = pattern.matcher("aA");
    System.out.println(matcher1.find());  //false

    Matcher matcher2 = pattern.matcher("bb");
    System.out.println(matcher2.find());  //true

    Matcher matcher3 = pattern.matcher("CC");
    System.out.println(matcher3.find());  //false

    Pattern pattern2 = Pattern.compile("^[^a-z]+$");
    Matcher matcher4 = pattern2.matcher("aA");
    System.out.println(matcher4.find());  //false

    Matcher matcher5 = pattern2.matcher("bb");
    System.out.println(matcher5.find());  //false

    Matcher matcher6 = pattern2.matcher("CC");
    System.out.println(matcher6.find());  //true

}

五. 捕获组

捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。捕获组有两种形式,一种是普通捕获组,另一种是命名捕获组,通常所说的捕获组指的是普通捕获组

17. 普通捕获组 如果没有显式为捕获组命名,即没有使用命名捕获组,那么需要按数字顺序来访问所有捕获组。在只有普通捕获组的情况下,捕获组的编号是按照“(”出现的顺序,从左到右,从1开始进行编号的 。编号为0的捕获组,指的是正则表达式整体,这一规则在支持捕获组的语言中,基本上都是适用的

    @Test
public void test12() {
    String pat = "\\S*(\\d{4})-(\\d{2}-(\\d{2}))\\S*";
    Pattern pattern = Pattern.compile(pat);
    Matcher matcher = pattern.matcher("今天是2018-12-13,我好开心");
    System.out.println(matcher.find());  //true
    System.out.println(matcher.group(0));  //今天是2018-12-13,我好开心
    System.out.println(matcher.group(1));  //2018
    System.out.println(matcher.group(2));  //12-13
    System.out.println(matcher.group(3));  //13
}

18. 命名捕获组 命名捕获组通过显式命名,可以通过组名方便的访问到指定的组,而不需要去一个个的数编号,同时避免了在正则表达式扩展过程中,捕获组的增加或减少对引用结果导致的不可控。不过容易忽略的是,命名捕获组也参与了编号的,在只有命名捕获组的情况下,捕获组的编号也是按照“(”出现的顺序,从左到右,从1开始进行编号的 。

@Test
public void test13() {
    String pat = "\\S*(?<year>\\d{4})-(?<date>\\d{2}-(?<day>\\d{2}))\\S*";
    Pattern pattern = Pattern.compile(pat);
    Matcher matcher = pattern.matcher("今天是2018-12-13,我好开心");
    System.out.println(matcher.find());  //true
    System.out.println(matcher.group(0));  //今天是2018-12-13,我好开心


    System.out.println(matcher.group(1));  //2018
    System.out.println(matcher.group("year"));  //2018

    System.out.println(matcher.group(2));  //12-13
      System.out.println(matcher.group("date"));  12-13

    System.out.println(matcher.group(3));  //13
    System.out.println(matcher.group("day"));  //13

}

上面所说的是是获取匹配,下面我们说费获取匹配。正向表示匹配前面,反向表示匹配后面。又叫零宽断言

19. (?:pattern) 匹配 pattern 但不获取匹配结果,也就是说这是一个非获取匹配,不进行存储供以后使用。这在使用 "或" 字符 (|) 来组合一个模式的各个部分是很有用

   @Test
public void test10() {
    String str = "industry|industries";
    String str1 = str.replaceAll("(industr(?:y|ies))", "");
    System.out.println(str1); //  | 说明都匹配到了
    String str2 = str.replaceAll("(industr[y|ies])", "");
    System.out.println(str2); //  |es 说明都匹配到了
}

20. (?=pattern) 正向肯定预查,在任何匹配pattern的字符串开始处匹配查找字符串。匹配pattern前面的位置

 @Test
public void test1() {
    String str = "windows95,windows100";
    //正向肯定预查,匹配windows95中的windows,或者windows1000中的windows
    str = str.replaceAll("(windows(?=95|1000))", "");
    System.out.println(str); //95,windows100
}

21. (?!pattern) 正向否定预查,在任何不匹配pattern的字符串开始处匹配查找字符串。匹配pattern后面的位置

   @Test
public void test2() {
    String str = "swindows95,rwindows100";
    //正向否定预查,不匹配windows95中的windows,或者不匹配windows1000中的windows
    str = str.replaceAll("(windows(?!95|1000))", "");
    System.out.println(str); //swindows95,r100
}

22. (?<=pattern) 反向肯定预查,与正向肯定预查类似,只是方向相反。匹配后面跟的不是pattern的位置

  @Test
public void test3() {
    String str = "95windows,100windows";
    //反向肯定预查,匹配95windows中的windows或者是1000windows中的windows
    str = str.replaceAll("(?<=95|1000)windows", "");
    System.out.println(str); //95,100windows
}

23. (?<!pattern) 反向否定预查,与正向否定预查类似,只是方向相反。匹配前面不是pattern的位置

@Test
public void test4() {
    String str = "95windows,100windows";
    //反向否定预查,不匹配95windows中的windows和1000windows中的windows
    str = str.replaceAll("(?<!95|1000)windows", "");
    System.out.println(str); //95windows,100
}

实战

1.压缩xml

我们发送的或者接收到的xml是有格式的。

<?xml version="1.0" encoding="GBK" standalone="yes"?>
<user>
    <header>
        <name>linyang</name>
        <age>26</age>
    </header>
    <body>
        <address>地址</address>
        <postcode>234321</postcode>
    </body>
</user>

我们需要将标签之间的空白字符都去掉,完成我们的压缩目的,一句代码搞定。

@Test
public void test40(){
    String str="<?xml version=\"1.0\" encoding=\"GBK\" standalone=\"yes\"?>\n" +
            "\<user>\n" +
            "    \<header>\n" +
            "        \<name>shuqi\</name>\n" +
            "        \<age>26\</age>\n" +
            "    \</header>\n" +
            "    \<body>\n" +
            "        \<address>地址\</address>\n" +
            "        \<postcode>234321\</postcode>\n" +
            "    \</body>\n" +
            "\</user>";
    str = str.replaceAll("(?<=>)\\s+(?=<)", "");
    System.out.println(str);
}

结果是:

<?xml version="1.0" encoding="GBK" standalone="yes"?><user><header><name>shuqi</name><age>26</age></header><body><address>地址</address><postcode>234321</postcode></body></user>

很好的完成了压缩的目的,利用的就是零宽断言。

本文收录的不是全部的正则表达式,只是相对来说比较常用的,他再文本匹配、搜索、替换都有较高的应用,掌握它我们会事半功倍。好玩的示例补充中,未完待续,敬请期待。

参考文档

正则表达式30分钟入门教程

正则基础之——捕获组(capture group)

表达式全集

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343