超级详细的正则表达式笔记(正则表达式基础+进阶Java源码)

首先引入基础正则表达式的基础语法

基础篇:

元字符

元字符是构造正则表达式的一种基本元素。

  • . :匹配除换行符以外的任意字符

  • \w:匹配字母或数字或下划线或汉字

  • \s:匹配任意的空白符

  • \d:匹配数字

  • \b:匹配单词的开始或结束

  • ^:匹配字符串的开始

  • $:匹配字符串的结束

案例:

  • 匹配8位数字的QQ号码:^\d\d\d\d\d\d\d\d$

  • 匹配1开头11位数字的手机号码:^1\d\d\d\d\d\d\d\d\d\d$

重复限定符

正则表达式提供了对重复字符进行简写的方式:

  • *:重复零次或更多次

  • +:重复一次或更多次

  • ?:重复零次或一次

  • {n}:重复n次

  • {n,}:重复n次或更多次

  • {n,m}:重复n到m次

有了这些限定符之后,我们就可以对之前的正则表达式进行改造了,比如:

  • 匹配8位数字的QQ号码:^\d{8}$

  • 匹配1开头11位数字的手机号码:^1\d{10}$

  • 匹配银行卡号是14~18位的数字:^\d{14,18}$

  • 匹配以a开头的,0个或多个b结尾的字符串:^ab*$

分组

限定符是作用在与他相邻的最左边的一个字符,那么问题来了,如果我想要ab同时被限定那怎么办呢?

正则表达式中用小括号()来做分组,也就是括号中的内容会作为一个整体。

如匹配字符串中包含0到多个ab开头:^(ab)*

转义

正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。

条件

回到我们刚才的手机号匹配,我们都知道:国内号码都来自三大运营商,它们都有属于自己的号段。

比如联通有130/131/132/155/156/185/186/145/176等号段,假如让我们匹配一个联通的号码,那按照我们目前所学到的正则,应该无从下手的,因为这里包含了一些并列的条件,也就是“或”,那么在正则中是如何表示“或”的呢?

正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。

那么我们就可以用或条件来处理这个问题:

^(130|131|132|155|156|185|186|145|176)\d{8}$
区间

正则提供一个元字符中括号 [] 来表示区间条件。

  • 限定0到9 可以写成[0-9]

  • 限定A-Z 写成[A-Z]

  • 限定某些数字 [165]

那上面的正则我们还改成这样:

^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$
反义

前面说到元字符的都是要匹配什么什么,当然如果你想反着来,不想匹配某些字符,正则也提供了一些常用的反义元字符:

元字符解释
\W匹配任意不是字母,数字,下划线,汉字的字符
\S匹配任意不是空白符的字符
\D匹配任意非数字的字符
\B匹配不是单词开头或结束的位置
[^x]匹配除了x以外的任意字符
[^aeiou]匹配除了aeiou这几个字母以外的任意字符
常用正则表达式
  • 匹配中文字符的正则表达式:[\u4e00-\u9fa5]

  • 匹配Email地址的正则表达式:^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$ 匹配形式:2035346428@qq.com]

  • 匹配国内电话号码:\d{3}-\d{8}|\d{4}-\d{7}匹配形式:匹配形式如 0511-4405222 或 021-87888822

  • 匹配腾讯QQ号:[1-9][0-9]{4,}匹配形式:2035346428

  • 匹配身份证:(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$ 匹配形式:142228199108252125

  • 匹配ip地址:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}匹配形式:127.0.0.1

  • 匹配国内的手机号:^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\d{8}$

    匹配形式:1388888888

Java中正则表达式用法

java.util.regex 包主要包括以下三个类:

  • Pattern 类:

  • 目的是传入要比较的内容

    正则表达式的编译表示形式。若要使用正则表达式必须将其【编译到此类】的实例中。然后,可以使用生成的模式对象创建 Matcher 对象。

  • Matcher 类:

    Matcher 对象是对输入字符串进行【解释和匹配】操作的引擎。与Pattern 类一样,Matcher 也没有公共构造方法。你需要调用 Pattern 对象的 matcher 方法来获得一个 Matcher 对象。

  • PatternSyntaxException:

    PatternSyntaxException 是一个非强制异常类,它表示一个正则表达式模式中的语法错误。

下面是几个小案例

案例一:匹配一个名字
//用Test进行测试,这里也可以换成main方法
@Test
    public void testRegex() {
        //查找
        String context = "i am xiaoHua, i com from HenNan.";
        //正则表达式,意思为:用来匹配包含子串 "xiaoHua" 的任何字符串,
        // 无论 "xiaoHua" 出现在该字符串的什么位置。
        //其中 .*:开始可以有零个或多个任意字符
        //     .*:在 "xiaoHua" 之后可以再跟零个或多个任意字符。
        String regex = ".*xiaoHua.*";
        //拿到将要进行比较的regex
        Pattern pattern = Pattern.compile(regex);
        //让regex与context进行匹配
        Matcher matcher = pattern.matcher(context);
        //matcher.matches() 整个context的内容,是否有与regex匹配的
        //是返回true 否则返回false
        System.out.println("matcher.matches() = " + matcher.matches());
    }
案例二:对文本内容进行替换
 @Test
    //替换算法
    public void testAppendReplace() {
        String regex = "xiaoHua";
        String context = "My name is xiaoHua.xiaoHua need studay hard!";
        String replacement = "xiaoHua";
        //跟上面的操作一样
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(context);
        //替换所有的xiaoHua
        String result1 = m.replaceAll(replacement);
        System.out.println(result1);
        //替换第一个出现的xiaoHua
        String result2 = m.replaceFirst(replacement);
        System.out.println(result2);
    }
案例三:匹配字符串并进行追加
 @Test
    public void AppendReplacement() {
​
        String regex = "a*b";
        String Input = "aabfooaabfooabfoobkkk";
        //
        String replacement = "-";
​
        Pattern p = Pattern.compile(regex);
        Matcher m = p.matcher(Input);
        //提前创建空字符串容器,然后把查找的结果放到这里
        StringBuffer sb = new StringBuffer();
​
        //改进方法是while循环,可以尝试写一下
        //        while (m.find()){
//            m.appendReplacement(sb,replacement);
//        }
​
        //查找满足regx的文本
        m.find();
        m.appendReplacement(sb, replacement);
        System.out.println(sb);
​
        //接着之前的再次查找
        m.find();
        m.appendReplacement(sb, replacement);
        System.out.println(sb);
​
        //剩下的尾巴加到sb里面
        m.appendTail(sb);
        System.out.println(sb);
​
    }
案例四:贪婪算法
@Test
//案例一
    public void testGreed(){
        //按照最多的进行匹配,首先找满足6个的。因此第一次满足的为123456,打印出来,之后再进行寻找“1”不满足最少的3个放弃。之后的每一次寻找都满足最大6个。打印结果为:
//匹配结果:123456
//匹配结果:123333
//匹配结果:123412
//匹配结果:333334
        String regex = "\\d{3,6}";
        String context = "1234561 123333 123412 333334";
        System.out.println("regex = "+ regex);
        System.out.println("context = "+ context);
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            
            System.out.println("匹配结果:" + matcher.group());
        }
​
    }
​
//案例二,同时满足条件
public void testGreed2(){
        //按照最多的进行匹配
       //需要同时满足最大的6跟7.首先匹配“123456”,满足第一个要求,然后匹配“1234561”,满足第二个最大要求,进行打印。接下来匹配“123333”,满足第一个,不满足第二个,不打印。以此类推。
        String regex = "\\d{3,6}\\d{4,7}";
        String context = "1234561 123333 123412 333334";
        System.out.println("regex = "+ regex);
        System.out.println("context = "+ context);
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
​
            System.out.println("匹配结果:" + matcher.group());
        }
​
    }
 
案例五:懒惰匹配
 @Test
//懒惰匹配语法(\\d{x,x+n}?),贪婪语法中括号后面加?.与贪婪匹配相反,它优先匹配最下的那一个。
    public void lazyTest(){
         //前面是惰性匹配,每次只匹配一个, 后面是贪婪匹配,一次匹配4个,第一次应该匹配五个
        String regex = "(\\d{1,2}?)(\\d{3,4})";
       //第一个满足前1后四,第二个不满足后面的最小3,第三个满足,第四个第五个不满足
        String context = "242342 123 1233 23 3";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            System.out.println(matcher.group(0));
        }
​
    }

进阶篇:

零宽断言

【断言】就是说正则可以【断定】在指定内容的前面或后面会出现满足指定规则的内容。

【零宽】 断言部分只确定位置不匹配任何内容,只是一种模式。内容宽度为零。

几个概念:

概念功能
预测/先行(模式在前),要求后面的符合匹配
回顾/后发(模式在后),要求前面的符合匹配
符合匹配
不符合匹配
正向先行断言

零宽度正预测先行断言

  • 语法:(?=pattern)

  • 作用:匹配pattern表达式的前面内容,不返回本身。

【正向先行断言】可以匹配表达式前面的内容,那意思就是(?=) 就可以匹配到前面的内容了。

如果我们要匹配所有内容那就是:

@Test
public void testAssert1(){
    //.+: 这里的 . 是一个特殊字符,表示“任意字符”,而 + 表示“一个或多个”。因此,.+ 意味着“一个或多个任意字符”。这是贪婪匹配,意味着它将尽可能多地匹配字符。
    //(?=</span>) 正预测先行断言
    String regex = ".+(?=</span>)";
    String context = "<span class=\"read-count\">阅读数:641</span>";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(context);
    while (matcher.find()){
        System.out.println(matcher.group());
    }
}
​
//匹配结果:<span class="read-count">阅读数:641
//如果只匹配数字 \d,那可以改成:
​
@Test
public void testAssert2(){
    String regex = "\\d+(?=</span>)";
    String context = "<span class=\"read-count\">阅读数:641</span>";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(context);
    while (matcher.find()){
        System.out.println(matcher.group());
    }
}
​
//匹配结果:
//641
正向后行断言

零宽度正回顾后发断言,断言在前,模式在后

  • 语法:(?<=pattern)

  • 作用:匹配pattern表达式的后面的内容,不返回本身。

有先行就有后行,先行是匹配前面的内容,那后行就是匹配后面的内容啦。

上面的例子,我们也可以用后行断言来处理:

@Test
public void testAssert3(){
    String regex = "(?<=<span class=\"read-count\">阅读数:)\\d+";
    String context = "<span class=\"read-count\">阅读数:641</span>";
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(context);
    while (matcher.find()){
        System.out.println(matcher.group());
    }
}
负向先行断言

零宽度负预测先行断言

  • 语法:(?!pattern)

  • 作用:匹配非pattern表达式的前面内容,不返回本身。

有正向也有负向,负向在这里其实就是非的意思。

举个栗子:比如有一句 “我爱祖国,我是祖国的花朵”。现在要找到不是'的花朵'前面的祖国。

用正则就可以这样写:祖国(?!的花朵)

 //负向先行断言
    @Test
    public void testAssert4(){
        //输出的祖国是 我爱祖国的这个“祖国”
        String regex = "祖国(?!的花朵)";
        String context = "我是祖国的花朵,我爱祖国";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }
负向后行断言

零宽度负回顾后发断言

  • 语法:(?<!pattern)

  • 作用:匹配非pattern表达式的后面内容,不返回本身。

举个例子:比如有一句 “我爱祖国,我是祖国的花朵”。现在要找到不是'我爱'后面的祖国。

用正则就可以这样写:(?<!我爱)祖国

  @Test
    public void testAssert5(){
        String regex = "(?<!我爱)祖国";
        String context = "我是祖国花朵,我爱祖国";

        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            System.out.println(matcher.group());
        }
    }
捕获

语法:(exp)

解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第0组为整个表达式,第一组开始为分组。

  • 比如固定电话的:020-85653333

  • 正则表达式为:(0\d{2})-(\d{8})

按照左括号的顺序,这个表达式有如下分组:

序号编号分组内容
00(0\d{2})-(\d{8})020-85653333
11(0\d{2})020
20(\d{8})85653333
 @Test
    public void testCatch(){
        String context = "020-23454344";
        String regex = "(0\\d{2})-(\\d{8})";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            for (int i = 0; i <= matcher.groupCount(); i++) {
                System.out.println(matcher.group(i));
            }

        }

    }


@Test
    //命名编号捕获组
    public void testCatch2(){
        String context = "020-23454344";
        String regex = "(?<areacode>0\\d{2})-(?<phone>\\d{8})";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            System.out.println("areacode = " + matcher.group("areacode"));

            System.out.println("phone = " + matcher.group("phone"));
        }
    }
//输出结果
//areacode = 020
//phone = 23454344
非捕获组:
  • 语法:(?:exp)

  • 解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组。

比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:(?:0\\d{2})-(\\d{8})

序号名称分组内容
00(0\d{2})-(\d{8})020-85653333
11(\d{8})85653333
 @Test
    //非捕获组
    public void testCatch3(){
        String context = "020-23454344";
        //0\d{2} 这个组将不再被捕获
        String regex = "(?:0\\d{2})-(\\d{8})";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(context);
        while (matcher.find()){
            for (int i = 0; i <= matcher.groupCount(); i++) {
                System.out.println(matcher.group(i));

            }
        }
    }

作业:输入一行字符串,判断该字符串是否为邮箱

public class TestEmail {
    public static void main(String[] args) {
        //先弄一个输入框
        Scanner scanner = new Scanner(System.in);
        String email = scanner.next();
        //判断是否为邮箱的正则表达式(网上有各种常用的表达式)
        String regex = "^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)+$";
        Pattern pattern = Pattern.compile(regex);
        Matcher matcher = pattern.matcher(email);
        boolean matches = matcher.matches();
        if (matches){
            System.out.println("您输入的是一个正确的邮箱!");
        }else {
            System.out.println("您输入的邮箱不正确!");
        }
    }
}

  • 21
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值