美文网首页Android开发啦啦啦啦啦!
八个题目带你看懂正则表达式

八个题目带你看懂正则表达式

作者: 九心_ | 来源:发表于2021-10-25 09:22 被阅读0次

    前言

    无论是写代码还是脚本,当我们要处理字符串或者提炼重要信息的时候,正则表达式都可以是我们的好帮手。

    不过很多同学都有一种这样的感触,正则 = 天书 ,比如下面的邮箱表达式:

    \w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*
    

    不知道你有没有晕,反正第一次我看的时候,这啥玩意!

    于是,当我们要使用正则的时候,第一选择肯定是网上复制已有的正则表达式,网上没有?不好意思,只能选择无脑一把梭的方法。

    技术

    比如我在写脚本的时候,要获取当前 maven 库中最新版本的信息,都是从 maven-metadata.xml 获取 <latest> 标签 :

    <metadata>
        <groupId>QDReader.QDAarCenter</groupId>
        <artifactId>QDUI_Component</artifactId>
        <versioning>
            <latest>0.0.10</latest>
            <release>0.0.10</release>
            <versions>
                <version>0.0.1</version>
                <version>0.0.2</version>
                <version>0.0.3</version>
                <version>0.0.4</version>
                <version>0.0.5</version>
                <version>0.0.6</version>
                <version>0.0.7</version>
                <version>0.0.8</version>
                <version>0.0.9</version>
                <version>0.0.10</version>
            </versions>
            <lastUpdated>20210817095518</lastUpdated>
        </versioning>
    </metadata>
    

    之前,我是先获取 <latest> 那一行,再使用 <> 做切割,最后取切割后的第三条,步骤属实有点多。

    而有了正则,通过 (?<=latest>)[\d\.]+ 就可以获取到我想要的内容!

    先分享给大家一个学习正则表达式的宝藏网站:

    regex101

    这个网站可以输入正则表达式和你想验证的文本,并将匹配的内容高亮,甚至会告诉你为什么会这么匹配,当然还有一些其他小技巧:

    网站地址:https://regex101.com/r/lL3C8c/2

    目录

    目录

    一、如何匹配字符

    方括号 [] 的使用频率很高,括号中的内容是用来匹配的,多个内容是或的关系,匹配其中的一项就算匹配成功。

    比如,我们使用 [World]W 可以匹配、o 也可以匹配,也就是说,World 它们是或的关系,匹配其中的一项就行,比如我们来验证老伙伴 Hello World,高亮部分就是匹配内容:

    匹配结果

    注意一下,匹配结果中灰色的点可不是点号,而是空格,可能这个网站只是为了让空格显著一点儿~

    常用的表达式如下:

    符号 解释
    [World] 匹配 World,可以替换成我们想要的任何字符
    [0-9] 匹配数字
    [a-z] 匹配小写字母,如果匹配大写字母,需要使用 [A-Z]
    [\s\S] 匹配所有,\s 匹配所有空白符,包括换行,\S 匹配非空白符,不包括换行
    [\u4e00-\u9fa5] 匹配所有汉字

    既然有想匹配括号中的内容,自然也就有了不想匹配括号中的内容。如果我不想匹配 [World] 中的任何一个字符,怎么办?

    简单,使用 ^ 符号,在 [] 中使用 ^ 就是取反的意思,上面的内容就变成了 [^World],验证一下 Hello World 就是:

    匹配结果

    很多情况下,仅仅匹配字母肯定不够,怎么匹配大小写字母加数字呢?

    加一起就行,[a-zA-Z0-9] 就表示匹配大小写字母和数字,那有没有更简单的写法呢?

    还真有,\w 表示 [a-zA-z0-9_],不过比上面的内容多了一个下划线,问题不大。除此以外,\d 表示匹配所有数字,. 就厉害了,它匹配出换行符(\n\r)的任何单个字符,整理一下:

    符号 解释
    [a-zA-Z0-9] 匹配单个大小写字母和数字
    \w 等价于 [a-zA-Z0-9_],匹配单个字母数字和下划线
    . 匹配除换行符之外的任何一个单个字符
    \d 匹配任何单个数字

    上面讲了这么多,不知道大家有没有发现,我们去查看匹配结果的时候,发现一次只能匹配一个值,比如我用 [am] 去匹配 I am JiuXinDev,发现结果是 am,而不是 am

    匹配结果

    因为 [] 只能匹配一个字符,下面我们再看如何匹配数量。

    二、如何匹配数量

    正则表达式常常用 {} 进行数量匹配,用法是:

    符号 解释
    {n} 匹配 n 次,例如 a{2} 可以匹配 aa,但是不能匹配 a
    {n,m} 匹配 n 到 m 次,例如 a{1,2} 既可以匹配 aa,也可以匹配 a
    {n,} 匹配至少 n 次,也就是 n 到正无穷,例如 a{2,},除了单个 a,其他多少个连续 a 都可以匹配

    这个时候,我们就可以匹配 I am JiuXinDev 中的 am 了,如果你仅仅想匹配 am 这个单词,文本字符 am 就可以搞定:

    image.png

    啊,这也太简单了,如果我们还想匹配 mac 中的 ma 呢,文本字符 am 就没有作用了,可以再加一个匹配项,中间使用 | 隔开,像这样 ma|am

    但再匹配 aamm 呢?我们刚刚学的 {} 就派上了用场,可以通过正则表达式 [am]{2} 去匹配 amma

    来做个练习

    题目1:怎么从一个句子中找出 2-5 个字母的单词?提醒一下,单词与空格之间的边界可以用 \b 表示

    字母对应着 [a-zA-Z],数量限制用 {n,m},2-5个单词的限制对应着 {2,5} ,加上边界,就是 \b[a-zA-Z]{2,5}\b

    匹配结果

    限定多少次才能匹配成功的叫做限定符,包括上面介绍的 {} 系列,还有一些简单版本的限定符:

    符号 解释
    ? 匹配0或1次,等价于{0,1}
    + 匹配1到无穷次,等价于 {1,}
    * 匹配0到无穷次,等价于 {0,}

    获取标签内容是常有的事儿

    题目2:给定<groupId>QDReader.QDAarCenter</groupId>, 如何提取 <groupId></groupId>

    还记得上面介绍过的万能匹配符 . 吗?我们只要保证必须以 < 开头 和 > 结尾,可以写成 <.*>

    匹配结果

    这结果不对啊,并不是我想要的匹配标签

    反了

    别急,再加个 ?,写成 <.*?>

    匹配结果

    一个 ?,涉及到了一个贪婪非贪婪的知识,简单来说:

    • 贪婪:尽可能多的匹配
    • 非贪婪匹配:尽可能少的匹配

    这么说还有点似懂非懂,拿上面的例子来讲,<groupId><groupId>QDReader.QDAarCenter</groupId> 都以 < 开头,以 > 结尾,如果是贪婪匹配,它的匹配结果是 <groupId>QDReader.QDAarCenter</groupId>,贪婪嘛,能匹配多的,绝不匹配少的;如果是非贪婪匹配,它有两个匹配结果,<groupId></groupId>

    本质上,+* 都是贪婪匹配,? 则是非贪婪匹配

    三、如何匹配位置

    定位符通常由下面这几种:

    符号 解释
    ^ 匹配以指定字符串开始的位置,可以指定一行文本或者整段文本以指定字符开始
    $ 匹配以指定字符串结束的位置,可以指定一行文本或者整段文本以指定字符结束
    \b 单词边界,字与空格的位置
    \B 非单词边界匹配,指字符与字符之间的边界

    \b 在上文已经用过,可以定位一个单词的开始和结束。^ 用在 [] 中进行字符匹配的时候,是取反的意思,我们也可以用来定位。

    重回本文开头的 maven-metadata.xml 文件:

    题目3:如何获取以 <version> 开头的该行所有内容?

    从上面的 maven-metadata.xml 可以看出,<version> 标签前其实是有若干空格符号的,可以用 \s* 表示,结合定位符 ^,整个正则表达式可以写成 ^\s*<version>.*

    匹配结果

    上面的解决方法还可以使用 $ 解决,即匹配以 </version> 结尾的所有行,正则表达式为 .*</version>$

    在一些情况下,^$ 还可以放在一起使用,例如 ^JiuXin$ 就可以用来表示匹配只有内容为 JiuXin 的行。

    四、如何匹配多内容

    多个选项的匹配一般使用选择来完成。

    1. 圆括号

    敲黑板,选择中最重要的就是圆括号 ()

    符号 解释
    () 可以将所有的选项放在括号里面,用 | 隔开,匹配多个选项中的一项就算成功。() 会将分组捕获,() 内匹配到的每一个词都会被放入缓存,通过数字查看对应的缓存

    这么听有点晕,我们挨个介绍一下。前半句听着有点像方括号 [] 的意思,多个选项,匹配其中的一项就算完成匹配,不过它们的区别是:

    • []:每个选项是一个字符,也只能匹配一个字符。
    • ():每个选项可以是一个字符,也可以是一个表达式,功能更强!

    比如,(am|\d+) 就表示匹配 am 或者不定数数字量的字符串。

    巩固一下:

    题目四,从给定的语句中,匹配以 a 和 b 开头的单词?

    提取单词用 \b,以 a 开头的单词对应着 a[a-zA-Z]*,匹配 a 或 b 可以用上面的选择表达式,结合一下就是 \b(a[a-zA-Z]*|b[a-zA-Z]*)\b

    匹配结果

    如果你注意到了匹配信息,会发现有点不一样的东西:

    匹配结果

    通常的匹配只有匹配结果 Match,这里却多了显示信息 Group,没错,它就是我们上面介绍的缓存,使用 \n 可以引用第 n 个缓存。

    我们可以使用这个缓存做点不一样的事,比如 ([a-zA-Z])\1 表示匹配两个连续的字符。

    巩固一下:

    题目五:给定的语句中,匹配两个连续的单词。

    表示一个单词可以用 \b\w+\b,因为要使用缓存 \1,所以得加上一个括号,组合在一起就是 (\b\w+\b)\s\1

    匹配结果

    使用缓存这种操作我们叫做反向引用

    2. 非捕获元

    上面提到了,使用 () 匹配的结果都会被缓存,而在括号中使用 ?: 就会消除缓存,非捕获元一共有如下几个:

    符号 解释
    ?: 在选择 () 中使用,去除缓存
    ?= exp1(?=exp2) 匹配以 exp2 结尾的 exp1
    ?! exp1(?!exp2) 匹配不以 exp2 结尾的 exp1
    ?<= (?<=exp2)exp1 匹配以 exp2 开头的 exp1
    ?!= (?!=exp2)exp1 匹配不以 exp2 开头的 exp1

    在介绍定位符的时候,我们介绍过 ^$,它们可以匹配整段和行开始和结束,也可以直接使用文本字符去匹配文本的开始和结束,但总觉得差点意思!

    图标

    那这里的 ?=?<= 有什么不同呢?

    非捕获元 ?<=?!=,顾名思义,指的匹配部分以指定内容开头或者结束,匹配部分不包括指定内容。比如 (?<=he)\w+ 匹配以 he 开头字符串,但匹配结果却不包含 he

    匹配结果

    还记得一开始的的题目吗?

    题目6:如何获取 maven 库中最新库的版本信息?(获取 <latest>0.0.10</latest> 中间的 0.0.10)

    有的版本号会包括字母和 -,所以版本号正则可以概括成 [\w\.-]+,加上以 <latest> 开头,可以写成 (?<=<latest>)[\w\.-]+

    匹配结果

    再来练习以什么结尾:

    题目7:如何获取QQ邮箱里面的QQ账号?

    首先,QQ邮箱一般以 qq.com 结尾,QQ账号一般5-11位数,第一个数字不是0(其他限制暂时没有想到),对应 [1-9]\d{4,10},两者结合就是 [1-9]\d{4,10}(?=@qq\.com)

    匹配结果

    到这里,我们应该可以看懂大部分的正则表达式了~

    五、如何按优先级匹配

    正则表达式从左往右计算,但是,它里面的各种符号也是有优先级的,由高到低如下:

    符号 解释
    \ 转义字符
    (), (?:), (?=), [] 圆括号和方括号
    *, +, ?, {n}, {n,}, {n,m} 限定符
    ^, $, \ 任何元字符、任何字符 定位点和序列
    | 或操作

    就跟我们写代码一样,运算符也有各种优先级,不过难度不大!

    实操一下:

    题目8:正则表达式 ((\d)([a-zA-Z]{2}(\d)))\1\2\3\4 和字符串 7ac87ac87ac88 可以匹配吗?如果可以,请输出它们的 \1\2\3\4

    这个正则表达式看着很复杂,好在它并没有使用 *+ 这两种限定符,所以匹配的结果的长度是一定的,之后我们按照优先级来看。

    最外层是一个大括号,因为它是从左往右的第一个括号,所以它匹配的结果 \1

    去除外层的括号,是 (\d)([a-zA-Z]{2}(\d)),还是从左往右,接下来先匹配左边的 (\d),所以它是 \2

    去除刚刚左边的 (\d),还有 ([a-zA-Z]{2}(\d)),最外层又是一个括号,跟第一种情况一样,它就是 \3

    最后还剩一个右边的 (\d),它是 \4

    仔细看一下整个表达式 (\d)([a-zA-Z]{2}(\d)),两边两个数字,中间是两个字母,一共四个数。\1 等于整个表达式,\2 + \3 等于 \1\4 是整个表达式最后一个数字。

    匹配结果

    所以最终结果是匹配的,匹配部分看上图就好。

    下期再见

    不会正则的时候,总觉得正则难,一顿操作下来,发现正则也就那么多东西。

    希望看完这篇文章的你,也能很快掌握正则!

    感谢阅读,下期再见 👋

    参考文章:

    《菜鸟正则教程》
    《正则表达式括号的作用》

    相关文章

      网友评论

        本文标题:八个题目带你看懂正则表达式

        本文链接:https://www.haomeiwen.com/subject/ikzvoltx.html