再续前缘之深入学习正则表达式

正则表达式

​ 最近几天再刷leetcode,遇到有些题觉得用正则就会把问题简化很多。于是想着把正则总结一下吧。

一、何为正则表达式

正则表达式**,又称规则表达式**。(英语:Regular Expression,在代码中常简写为regex、regexp或RE),计算机科学的一个概念。正则表达式通常被用来检索、替换那些符合某个模式(规则)的文本。

通过百度搜索一翻,可以了解到如上书面性的解释。记得我刚开始接触正则表达式的时候,那时候看到这句话,也不能理解。说的太官方了。当然了,纸上得来终觉浅。一切都需要我们去一步步学习。正则是个很强的工具,学会了他,你可以用正则去分析别人的网页去爬取你想要的资源。当然,现在爬虫的库已经很多了,我们几乎不用正则表达式,也可以爬取到相应的资源。可是,我们还是不能停止对正则的研究。因为有一天你也想要学会如何造轮子,或者你自己写的小项目对于字符串的需要分析的时候,你自然而然就会想到正则表达式。

二、正则表达式的作用

  • 正则表达式(regular expression)就是用一个“字符串”来描述一个特征,然后去验证另一个“字符串”是否符合这个特征。

    比如 表达式“ab+” 描述的特征是“一个 ‘a’ 和 任意个 ‘b’ ”,那么 ‘ab’, ‘abb’, ‘abbbbbbbbbb’ 都符合这个特征。

    注意:这里的 + 号代表 前面的字符出现零次或者更多次。所以ab后面不管出现多少个b都会匹配到该字符串。这就是正则表达式的魅力。

    一般来说,正则表达式可以为我们提供如下:

    (1)验证字符串是否符合指定特征,比如验证是否是合法的邮件地址。
    (2)用来查找字符串,从一个长的文本中查找符合指定特征的字符串,比查找固定字符串更加灵活方便。

    (3)用来替换,比普通的替换更强大。

这里举个例子,如果让你判断一个字符串是否满足QQ邮箱的条件?

如果你没有学过正则,你可能会写类似如下的代码…:(只提供java语言实现)

/*
 *
 * @param email要验证的QQ邮箱
 * @author raven
*/
private boolean checkEmail(String email){
    boolean res = true;
    if(email == null || email.equals("")|| !email.contains("@"))
        res = false;
        return res ;
    String array[] = email.splint("@");
    // 如果不是qq.com 为后缀
    if( !array[1].containsIgnoreCase("qq.com")){
        res = false;
        return res;
    }
    // 获取邮箱前缀。我们都知道该前缀一般为 数字。为了简化问题,此处不考虑 其他字符形式。因为QQ邮箱前缀一般都是QQ。
    String num = array[0];
    for( int i = 0; i < num.length(); i ++ ){
        //没有0开头的QQ
        if( i == 0 &&  num.charAt(i) == '0' ){
            res = false;
        }
        char current = num.charAt(i);
        // 如果当前字符的ascii码 不是数字
        if( current < 0x30 || current > 0x39 ){
            res = false;
        }
    }
    return res;
}

​ emm,其实代码写出来也挺漂亮的。写的蛮舒服的。可是,这就是判断个邮箱类型啊,就写了这么多代码,要知道,现在的业务,字段属性都很多的,如果每个都要这么校验,那还不累死你。你这时候就会想。哎,我要是会正则就好了啊。那你会说我:你老是吹正则怎么怎么样,那你给我用正则写个呗,我看看。

​ 行,那我就写个给你看看。

java版:

private boolean checkEmail(String email){
   Pattern.matches("^[1-9]\\d{4,10}@(qq|QQ)\\.com$", email)
}

javaScript版:

/*
 *
 * @param str 要验证的QQ邮箱
*/
function checkEmail(str){
    var parse = /^[1-9]\d{4,10}@(qq|QQ)\.com$/;
    return parse.test(str)
}

​ emm,看看就是 这么简单。一句代码完成了该功能。什么,你说你看不懂?那我给你讲讲就好了。

​ 首先呢,^ 代表 字符串的匹配起始符。也就是说从字符串的第一个字符开始匹配。其次[1-9]代表第一个次数是0-9的任意一个数字。\d{4,10}又是什么呢?这个就是匹配第二个以后的字符 \d代表数字[0-9],后面的{4,10}代表数字可能出现4-10次。为什么是4-10次呢?因为第一位我们前面刚才写了[1-9]已经占了一个长度了,再加后面的数字循环区间[4,10],故匹配的数字长度就是 [5-11]了,符合QQ的长度。后面的@照写。紧跟着(qq|QQ)相当于判断。这里要么是大写的,要么是小写的。最后直接写个.com结束正则表达式的简写。再加最有一个$代表着匹配结束符。

​ 所以呢,就是 这么简单。正则是不是真香,再也不用写那么多代码了。就算你那样写出来效率也比正则低。所以 ,正则真的是个法宝。但是正则有一个缺点,就是不容易理解。哈哈哈,其实用习惯就会发现好多了。

这里分享几个正则的元字符,大家记着记好了:

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

2.[]:是字符集合,表示匹配方括号中所包含的任意一个字符

   [0123456789]   表示匹配任意一个数字
    [0-9]   表示匹配任意一个数字
    [yml]   表示匹配'y','m','l'中任意一个字符
    [a-z]   表示匹配任意一个小写字符
    [A-Z]   表示匹配任意一个大写字符
    [0-9a-zA-Z]    表示匹配任意一个数字和字母
    [0-9a-zA-Z_]    表示匹配任意一个数字、字母和下划线

3.^:是脱字符,表示不匹配集合中的字符,注意^在[]中才表示不匹配,在其他位置意义不一样

        [^test]  表示匹配除了test这几个字母以外的所有字符
        [^0-9]  表示匹配所有的非数字字符

4.\d:匹配数字,效果同[0-9]

        \d       表示匹配任意一个数字,这里可以不写[],直接使用\d
        [^\d]   表示匹配所有的非数字字符,效果同[^0-9]

5.\D:匹配非数字字符,效果同[^0-9]

6.\w:匹配数字、字母和下划线,效果同[0-9a-zA-Z]

7.\W:匹配非数字、字母和下划线,效果同[^0-9a-zA-Z]

8.\s:匹配任意的空白符(空格、换行、回车、换页、制表符),效果同[ \f\n\r\t]

9.\S:匹配任意的非空白符(空格、换行、回车、换页、制表符),效果同[^ \f\n\r\t]

10. \b 匹配字母或数字边界 [\b] 匹配转义字符\b本身,匹配退格键 \u0008 

11. \B 匹配非字母和数字边界

12. ^ 行首匹配,注意^在[]中才表示不匹配集合中的字符

13. $ 行尾匹配
14. \A 从字符串开始处匹配
1. (xyz) 匹配小括号内的内容,()内的内容xyz作为一个整体去匹配

2. x? 匹配0个或者1个x,非贪婪匹配(尽可能少的匹配)

3. x* 匹配0个或者任意多个x,贪婪匹配(尽可能多的匹配)

    .* 表示匹配0个或任意多个字符(换行符除外)

4. x+ 匹配至少一个x,贪婪匹配(尽可能多的匹配)

5. x{n} 匹配确定的n个x,n是一个非负整数

6. x{n,} 匹配至少n个x,n是一个非负整数

7. x{n,m} 匹配至少n个最多m个x,注意:n <= m

8. x|y |表示或,匹配的是x或y

三、捕获组 与 非 捕获组

首先,要弄清楚什么是捕获组什么是非捕获组。

​ 捕获组就是把正则表达式中子表达式匹配的内容,保存到内存中以数字编号或显式命名的组里,方便后面引用。()可以声明一个子表达式为捕获组。

​ 但是有时候,因为特殊原因用到了(),但又没有引用它的必要,这时候就可以用非捕获组声明,防止它作为捕获组,降低内存浪费。?:可以声明一个子表达式为非捕获组。

以下是 捕获组java 中的使用:

public static final String data = "2017-04-25";
public static final String ngex = "(\\d{4})-((\\d{2})-(\\d{2}))";

Pattern pattern = Pattern.compile(ngex);
Matcher matcher = pattern.matcher(data);
matcher.find();
//输出===>2017-04-25
System.out.printf("matcher.group(0) value:%s", matcher.group(0));
//输出===>2017
System.out.printf("matcher.group(1) value:%s", matcher.group(1));
//输出===>04-25
System.out.printf("matcher.group(2) value:%s", matcher.group(2));
//输出===>04
System.out.printf("matcher.group(3) value:%s", matcher.group(3));
//输出===>25
System.out.printf("matcher.group(4) value:%s", matcher.group(4));

3.1、捕获组命名

(?<捕获组名称>\\Regex命令)捕获组命令,这样获取就更方便了,不用拿下标获取捕获组的值。

public static final String P_NAMED = "(?<year>\\d{4})-(?<md>(?<month>\\d{2})-(?<date>\\d{2}))";
public static final String DATE_STRING = "2017-04-25";

Pattern pattern = Pattern.compile(P_NAMED);
Matcher matcher = pattern.matcher(DATE_STRING);
matcher.find();
System.out.printf("===========使用名称获取=============");
// 输出===>2017-04-25
System.out.printf("matcher.group(0) value:%s", matcher.group(0));
// 输出===>2017
System.out.printf(" matcher.group('year') value:%s", matcher.group("year"));
// 输出===>04-25
System.out.printf("matcher.group('md') value:%s", matcher.group("md"));
// 输出===> 04
System.out.printf("matcher.group('month') value:%s", matcher.group("month"));
// 输出===> 25
System.out.printf("matcher.group('date') value:%s", matcher.group("date"));
matcher.reset();
// 下面输出的值跟上面的一样 就不注释了。
System.out.printfln("===========使用编号获取=============");
matcher.find();
System.out.printfln("matcher.group(0) value:%s", matcher.group(0));
System.out.printfln("matcher.group(1) value:%s", matcher.group(1));
System.out.printfln("matcher.group(2) value:%s", matcher.group(2));
System.out.printfln("matcher.group(3) value:%s", matcher.group(3));
System.out.printfln("matcher.group(4) value:%s", matcher.group(4));

3.2 、非捕获组

public static final String P_UNCAP = "(?:\\d{4})-((\\d{2})-(\\d{2}))";
public static final String DATE_STRING = "2017-04-25";

Pattern pattern = Pattern.compile(P_UNCAP);
Matcher matcher = pattern.matcher(DATE_STRING);
matcher.find();
// 输出 ==> 2017-04-25
System.out.printfln("matcher.group(0) value:%s", matcher.group(0));
// 输出 ==> 04-25
System.out.printfln("matcher.group(1) value:%s", matcher.group(1));
// 输出 ==> 04
System.out.printfln("matcher.group(2) value:%s", matcher.group(2));
// 输出 ==> 25
System.out.printfln("matcher.group(3) value:%s", matcher.group(3));

四、反向引用

​ 所谓的反向引用,即就是 当后面 要用到的 的 正则表达式元字符组合 跟 前面的一样,那么可以重复利用。只要把需要 引用的 元字符 组成 捕获组,即可引用。

以下是 反向引用javaScript 中的使用:

var str = 'ooo<div>hello world!</div>234';
// 正则表达式里的 \1 代表引用 正则表达式的第一个捕获组
var reg = /<(\w+).*<\/\1>/g;
 
var result = str.match(reg);
 // 输出==> <div>hello world!</div>
console.log(result[0]);

以下是 反向引用java 中的使用:

String content="<tr>hello world!</tr>";
		String regextStr="<(\\w+)>.*?</\\1>";
		Pattern pattern=Pattern.compile(regextStr, Pattern.CASE_INSENSITIVE);
		Matcher matcher =pattern.matcher(content);
		if(matcher.find()){
            // 输出==> <tr>hello world!</tr>
			System.out.println(matcher.group(0));
            // 输出==> <tr>
			System.out.println(matcher.group(1));
			
		}else{
			System.out.println("no found");
		}

五、环视

环视强调位置(前面或后面),必须匹配环视表达式,才能匹配成功。

环视可认为是虚拟加入到它所在位置的附加判断条件,并不消耗正则的匹配字符。

类型 正则表达式 匹配成功条件
肯定逆序环视 (?<=expresion) 匹配子表达式右侧的位置
否定逆序环视 (?<!expresion) 匹配非子表达式右侧的位置
肯定顺序环视 (?=expresion) 匹配子表达式左侧的位置
否定顺序环视 (?!expresion) 匹配非子表达式左侧的位置

5.1 肯定逆序环视

(?<=a)代表 找到字符a右边的引索位置

 		String str = "f123a123";
        String pattern = Pattern.compile("(?<=a)");
        String matcher = pattern.matcher(str);
		//输出 ==> f123a,123
        System.out.println(matcher.replaceAll(","));

5.2 否定逆序环视

(?<=a)代表 不是字符a右边的引索位置,所以 只要不是a字符,那么该字符的右边必然有个逗号

 		String str = "f1a2c3a4";
        String pattern = Pattern.compile("(?<!a)");
        String matcher = pattern.matcher(str);
		//输出 ==> ,f,1,a2,c,3,a4,
        System.out.println(matcher.replaceAll(","));

5.3 肯定顺序环视

(?=a)代表 a字符左边位置的索引位置。所以每个a字符左边都有个 逗号。

		String str = "f1a2c3a4";
        String pattern = Pattern.compile("(?=a)");
        String matcher = pattern.matcher(str);
		//输出 ==> f1,a2c3,a4
        System.out.println(matcher.replaceAll(","));

5.4 否定顺序环视

(?=a)代表 非 a字符左边位置的索引位置。所以每个非a字符左边都有个 逗号。

        String str = "f1a2c3a4";
        String pattern = Pattern.compile("(?!a)");
        String matcher = pattern.matcher(str);
		//输出 ==> ,f,1a,2,c,3a,4,
        System.out.println(matcher.replaceAll(","));

六、总结

​ 可以看出来,正则表达式的内容也不是很多。但是我们熟悉掌握了它。对我们的帮助很大呢。对于 捕获组和非捕获组这一块的内容,也不是很难理解。把上面的例子搞懂了就很好理解了。by 2020.2.3

发布了63 篇原创文章 · 获赞 149 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/huijiaaa1/article/details/104160923
今日推荐