正则表达式学习笔记
正则表达式学习笔记
原文地址在此,本文只作表达上的精简,并且去掉了语言实现的细节。
元字符
常用的元字符:
元字符 | 说明 |
---|---|
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串结束 |
有了元字符之后,我们就可以利用这些元字符来写一些简单的正则表达式了,
比如:
匹配有abc开头的字符串:
\babc 或者 ^abc
匹配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到m次 |
使用重复限定符改进上面的正则表达式:
匹配8位数字的QQ号码:
^\d{8}$
匹配1开头11位数字的手机号码:
^1\d{10}$
匹配银行卡号是14~18位的数字:
^\d{14,18}$
匹配以a开头的,0个或多个b结尾的字符串
^ab*$
分组
如果想要多个字符同时被限定,可以使用分组功能。
正则表达式中用小括号 () 来做分组,也就是括号中的内容作为一个整体。
因此当我们要匹配多个 ab 时,我们可以这样。
如:匹配字符串中包含 0 到多个 ab 开头:
^(ab)*
转义
我们看到正则表达式用小括号来做分组,那么问题来了:
如果要匹配的字符串中本身就包含小括号,那是不是冲突?应该怎么办?
针对这种情况,正则提供了转义的方式,也就是要把这些元字符、限定符或者关键字转义成普通的字符,做法很简答,就是在要转义的字符前面加个斜杠,也就是\即可。如:要匹配以 (ab) 开头:
^(\(ab\))*
条件或
正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。
用或条件来处理手机号匹配问题:
^(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}$
零宽断言
无论是零宽还是断言,听起来都古古怪怪的,那先解释一下这两个词。
- 断言:俗话的断言就是“我断定什么什么”,而正则中的断言,就是说正则可以指明在指定的内容的前面或后面会出现满足指定规则的内容,意思正则也可以像人类那样断定什么什么,比如"ss1aa2bb3", 正则可以用断言找出 aa2 前面有 bb3,也可以找出 aa2 后面有 ss1.
- 零宽:就是没有宽度,在正则中,断言只是匹配位置,不占字符,也就是说,匹配结果里是不会返回断言本身。
例子:抓取文章阅读量
<span class="read-count">阅读数:641</span>
我们可以使用正则表达式来获取里面 641 这个数字。
正向先行断言(正前瞻)
语法:(?=pattern)
作用:匹配 pattern 表达式的前面内容,不返回本身。
正则表达式:".+(?=</span>)"
匹配结果:<span class="read-count">阅读数:641
获取阅读数字只需稍作修改:
正则表达式:"\\d+(?=</span>)"
匹配结果: 641
正向后行断言(正后顾)
语法:(?<=pattern)
作用:匹配 pattern 表达式的后面的内容,不返回本身。
正则表达式:"/(?<=<span class="read-count">阅读数:)\d+"
匹配结果: 641
负向先行断言(负前瞻)
语法:(?!pattern)
作用:匹配非 pattern 表达式的前面内容,不返回本身。
有正向也有负向,负向在这里其实就是非的意思。
举个栗子:比如有一句 “我爱祖国,我是祖国的花朵”,现在要找到不是'的花朵'前面的祖国。
用正则就可以这样写:
祖国(?!的花朵)
负向后行断言(负后顾)
语法:(?<!pattern)
作用:匹配非 pattern 表达式的后面内容,不返回本身。
捕获和非捕获
单纯说到捕获,他的意思是匹配表达式,但捕获通常和分组联系在一起,也就是“捕获组”。
- 捕获组:匹配子表达式的内容,把匹配结果保存到内存中中数字编号或显示命名的组里,以深度优先进行编号,之后可以通过序号或名称来使用这些匹配结果。
而根据命名方式的不同,又可以分为两种组:
数字编号捕获组:
语法:(exp)
解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第 0 组为整个表达式,第一组开始为分组。
比如固定电话的:020-85653333
他的正则表达式为:(0\d{2})-(\d{8})
按照左括号的顺序,这个表达式有如下分组:
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (0\d{2}) | 020 |
2 | 2 | (\d{8}) | 85653333 |
可见,分组个数是2,但是因为第0个为整个表达式本身,因此也一起输出了。
命名编号捕获组
语法:(?<name>exp)
解释:分组的命名由表达式中的 name 指定
比如区号也可以这样写:(?<quhao>\0\d{2})-(?<haoma>\d{8})
,按照左括号的顺序,这个表达式有如下分组:
序号 | 名称 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | quhao | (0\d{2}) | 020 |
2 | haoma | (\d{8}) | 85653333 |
非捕获组
语法:(?:exp)
解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组,说的通俗一点,就是你可以根据需要去保存你的分组。
比如上面的正则表达式,程序不需要用到第一个分组,那就可以这样写:
(?:\0\d{2})-(\d{8})
序号 | 编号 | 分组 | 内容 |
---|---|---|---|
0 | 0 | (0\d{2})-(\d{8}) | 020-85653333 |
1 | 1 | (\d{8}) | 85653333 |
反向引用
上面讲到捕获,我们知道:捕获会返回一个捕获组,这个分组是保存在内存中,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。
根据捕获组的命名规则,反向引用可分为:
- 数字编号组反向引用:\k 或\number
- 命名编号组反向引用:\k 或者'name'
捕获组一般和反向引用同时使用,主要作用是查找一些重复的内容或者做替换指定字符。
例:查找"aabbbbgbddesddfiid"中重复的字母。
正则表达式:"(\w)\1"
匹配结果:
aa
bb
bb
dd
dd
ii
贪婪和非贪婪
贪婪
我们都知道,贪婪就是不满足,尽可能多的要。在正则中,贪婪也是差不多的意思:
- 贪婪匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符,这匹配方式叫做贪婪匹配。
- 特性:一次性读入整个字符串进行匹配,每当不匹配就舍弃最右边一个字符,继续匹配,依次匹配和舍弃(这种匹配 - 舍弃的方式也叫做回溯),直到匹配成功或者把整个字符串舍弃完为止,因此它是一种最大化的数据返回,能多不会少。
前面我们讲过重复限定符,其实这些限定符就是贪婪量词,比如表达式:
\d{3,6}
用来匹配3到6位数字,在这种情况下,它是一种贪婪模式的匹配,也就是假如字符串里有6个个数字可以匹配,那它就是全部匹配到。
//对文本返回所有的匹配结果
文本:61762828 176 2991 44 871
贪婪模式:\d{3,6}
匹配结果:617628
匹配结果:176
匹配结果:2991
匹配结果:871
由结果可见:本来字符串中的“61762828”这一段,其实只需要出现3个(617)就已经匹配成功了的,但是他并不满足,而是匹配到了最大能匹配的字符,也就是6个。
一个量词就如此贪婪了,那有人会问,如果多个贪婪量词凑在一起,那他们是如何支配自己的匹配权的呢?
是这样的,多个贪婪在一起时,如果字符串能满足他们各自最大程度的匹配时,就互不干扰,但如果不能满足时,会根据深度优先原则,也就是从左到右的每一个贪婪量词,优先最大数量的满足,剩余再分配下一个量词匹配。
文本:61762828 176 2991 87321
贪婪模式:(\d{1,2})(\d{3,4})
匹配结果:617628
匹配结果:2991
匹配结果:87321
“617628” 是前面的\d{1,2}匹配出了 61,后面的匹配出了 7628
"2991" 是前面的\d{1,2}匹配出了 29 ,后面的匹配出了 91
"87321"是前面的\d{1,2}匹配出了 87,后面的匹配出了 321
懒惰(非贪婪)
懒惰匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能少的字符,这匹配方式叫做懒惰匹配。
特性:从左到右,从字符串的最左边开始匹配,每次试图不读入字符匹配,匹配成功,则完成匹配,否则读入一个字符再匹配,依此循环(读入字符、匹配)直到匹配成功或者把字符串的字符匹配完为止。
懒惰量词是在贪婪量词后面加个“?”
代码 | 说明 |
---|---|
*? | 重复任意次,但尽可能少重复 |
+? | 重复1次或更多次,但尽可能少重复 |
?? | 重复0次或1次,但尽可能少重复 |
{n,m}? | 重复n到m次,但尽可能少重复 |
{n,}? | 重复n次以上,但尽可能少重复 |
文本:61762828 176 2991 87321
贪婪模式:(\d{1,2}?)(\d{3,4})
匹配结果:61762
匹配结果:2991
匹配结果:87321
解答:
“61762” 是左边的懒惰匹配出 6,右边的贪婪匹配出 1762
"2991" 是左边的懒惰匹配出 2,右边的贪婪匹配出 991
"87321" 左边的懒惰匹配出 8,右边的贪婪匹配出 7321
反义
前面说到元字符的都是要匹配什么什么,当然如果你想反着来,不想匹配某些字符,正则也提供了一些常用的反义元字符:
元字符 | 解释 |
---|---|
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |