文章目录
一、com
正则表达式是一种格式化的文本,用来描述指定格式的字符串的字符串
描述字符串最好的方式就是一字不差的写出来,但这样效率太低,因此需要提取字符串的规律,那就要:
有分类和简写,比如所有的字母、数字
有转义
有重复次数,比如0个、n个
有逻辑运算符
有位置,前后
有大小写
本文从需求的角度来剖析regex
须知
- 正则的意思是正规、规则。正则表达式的英文名是 Regular Expression,可以直译为描述某种规则的表达式,一般缩写为 regex或“regexp”。
- 由数字、字母、特殊符号组成,大小写敏感
- 从左到右匹配
- 分为基本和扩展
场景
- 校验输入是否符合规则,比如表单验证
- 提取符合条件文本,比如爬虫
glob模式
我们都知道正则表达式非常复杂,正则表达式入门这一本书足足有好几百页这么厚。但是实际上我们一般情况往下用不到这么复杂的模式匹配,所以我们在shell命令当中常用的简化了的模式匹配规则,它比正则表达式要简单很多。
比如*可以代表一切的字符串,可以是0个也可以是任意多个字符,在普通正则中,只代表重复无数次。
[abc]表示匹配方括号当中的任何一个字符,
?表示匹配任何一个字符。
[0-9]表示匹配0-9当中任意一个数字,
两个号表示任何中间目录,比如src/**/build,可以匹配到src/test/build,也可以匹配到src/current/build。
glob模式没有简写,\d这种都不能用。
二、抽象某一类字符:元字符的抽象符
正则的世界中,所有字符分为2类,一类是普通字符,这些只能匹配他们自身。还有就是元字符,能匹配一类字符,还能对位置、数量等进行限制,这些叫元字符。当然,元字符也能匹配他们自身,只是需要转义而已。
普通字符这个不用学,但元字符肯定是要学的:
1.元字符
类似“+”的用来描述匹配要求的字符,称之为元字符。其中,“+”这类描述匹配次数的元字符,称为限制符。
元字符 | 描述 |
---|---|
. | 句号匹配任意单个字符除了换行符。 |
[ ] | 字符类。匹配方括号内的任意单个字符。 |
[^ ] | 否定的字符种类。匹配除了方括号里的任意字符 |
* | 匹配>=0个重复的在*号之前的字符。 |
+ | 匹配>=1个重复的+号前的字符。 |
? | 标记?之前的字符为可选. |
{n,m} | 匹配num个大括号之间的字符 (n <= num <= m). |
(xyz) | 字符集,匹配与 xyz 完全相等的字符串. |
| | 或运算符,匹配符号前或后的字符. |
\ | 转义字符,用于匹配一些保留的字符 [ ] ( ) { } . * + ? ^ $ \ |
^ | 从开始行开始匹配. |
$ | 从末端开始匹配. |
2.简写字符集、快捷方式
须知:
一般在字符类和分组中用
没有字母的简写
简写字符、快捷方式取反:通过大小写
简写 | 描述 |
---|---|
. | 除换行符外的所有字符 |
\w | 匹配所有字母数字,等同于 [a-zA-Z0-9] |
\W | 匹配所有非字母数字,即符号,等同于: [^\w] |
\b | 匹配一个零宽单词边界,如一个字母与一个空格之间;例如,/\bno/ 匹配 “at noon” 中的 “no”,/ly\b/ 匹配 “possibly yesterday.” 中的 “ly” |
\B | 匹配一个零宽非单词边界,如两个字母之间或两个空格之间;例如,/\Bon/ 匹配 “at noon” 中的 “on”,/ye\B/ 匹配 "possibly yesterday."中的 “ye” |
\d | 匹配数字: [0-9] |
\D | 匹配非数字: [^\d] |
\s | 匹配所有空白符,包括空格、制表符、换页符、换行符和其他 Unicode 空格。等价于: [\t\n\f\r\p{Z}] |
\S | 匹配所有非空格字符: [^\s] |
\f | 匹配一个换页符 |
\n | 匹配一个换行符 |
\r | 匹配一个回车符 |
\t | 匹配一个制表符 |
\v | 匹配一个垂直制表符 |
\p | 匹配 CR/LF(等同于 \r\n),用来匹配 DOS 行终止符 |
\b | 单词边界 |
3.任意单个字符:点
.
是元字符中最简单的例子。 .匹配任意单个字符
,但不匹配换行符,但不能在字符集中
。 例如,表达式.ar匹配一个任意字符后面跟着是a和r的字符串。
“.ar” => The car
par
ked in the gar
age.
4.单个字符之间为或:字符集/字符类[]
用方括号[ ]
包裹的一组字符,里面的字符无序,与特征标群区分,相当于带 或|
的字符集。[xxx]等价于(x|x|x)
。用-
表示包头包尾的范围。
“[Tt]he” => The
car parked in the
garage.
“ar[.]” => A garage is a good place to park a car
.
{1}否定字符类
一般 ^ 表示一个字符串的开头
^23{2,}|23{2,}$
表示位于一个字符串的开头或结尾部分 且
这个开头或结尾部分是23。
但它用在一个方括号
的开头的时候,它表示这个字符集是否定的。 例如,表达式[^c]ar 匹配一个后面跟着ar的除了c的任意字符。
“[^c]ar” => The car par
ked in the gar
age.
{2}example
[xyz]:匹配 “x"或"y"或"z”
[^xyz]:补集,匹配除 “x” “y” "z"的其他任意字符
[a-z]:匹配从 “a” 到 “z” 的小写英文字母
[^a-n]:补集,匹配除 “a” 到 “n” 的其他字符
[A-Z]:匹配从 “A” 到 “Z” 的任意字符
[1-9]:匹配从 “1” 到 “9” 的任意数字
/[a-zA-Z0-9]/ 或者 /[a-z0-9]/i:匹配所有的字母和数字可以写成
^23{2,}|23{2,}$
表示位于一个字符串的开头或结尾部分 且
这个开头或结尾部分是23。
5.多个字符之间为或:()和|,括号不仅仅指分组,还有特征标群和捕获符号的作用
“(f|c|m)at.?” => The fat
cat
sat on the mat.
[] 表示的是单个字符的或,那多个字符的或,用()来表达。可以用来分组,比如A出现2次,BC出现3次,可以(A{2})(BC{3}),将BC作为一个整体来表达
特征标群是一组写在 (…) 中的子模式。表示一个整体,就是一个里面的内容是1个整体,可以对这个整体进行次数、起止等描述。
例如之前说的 {} 是用来表示前面一个字符出现指定次数。但如果在 {} 前加入特征标群则表示整个标群内的字符重复 N 次。例如,表达式 (ab)* 匹配连续出现 0 或更多个 ab。
我们还可以在 () 中用或字符 | 表示或。例如,(c|g|p)ar 匹配 car 或 gar 或 par.
“(c|g|p)ar” => The car
is par
ked in the gar
age.
(ab)
表示ab整体而不像[ab]
那样只匹配其中1个
6.转义
右斜线表示转义后面的字符或特征标群,用来表示{ } [ ] / \ + * . $ ^ | ?
这些特殊字符也是文本。
因为\本身也是元字符,所以\
三、位置(xx的前面,以yy开头)
1.锚点,边界
用来描述字符串的起止。在非[]中,^ 指定开头,$ 指定结尾。
2.环视(零宽度断言、前后预查)
{1}com
https://blog.csdn.net/weixin_46927507/article/details/114037804
一般正则都是匹配文本,但有些场景需要匹配位置,比如字母a的位置,而不是字母a本身。
既然是位置,那么肯定就有方向了,a前面叫lookahead前视,a后面叫lookbehind后视。
所以环视的套路就是,先匹配一些文本,然后用一些符号来表名这个文本的前面还是后面。
环视肯定要向不同的方向看,在正则里没有上下,所以就是向前后看。
环视让条件表达能力更强,原来是匹配xx文本,现在是在xx文本的前面,并且匹配yy文本。
前后,是谁的前后?就是符号后面的那个字符。
既然有xx文本的前面 (?=xx),那肯定就有不是xx文本的前面(?!xx)
(?<=xx)、(?<!xx)
{2}syntax
必须出现在()中
?= 向pattern的前面查看,?<= 向pattern的后面查看。有箭头的是后。符号的前面和后面是想要的
{3}总结
req | pattern |
---|---|
xx文本的前面并且是数字 | \d+(?=xx) |
xx文本的后面并且是数字 | (?<=xx)\d+ |
不是xx文本的前面并且是数字 | \d+(?!xx) |
不是xx文本的后面并且是数字 | (?<!xx)\d+ |
四、表示数量:量词
1. 重复m次
在正则表达式中 {} 是一个量词,常用来一个或一组字符可以重复出现的次数。 例如, 表达式 [0-9]{2,3} 匹配2到3位 0~9 的数字。
“[0-9]{2,3}” => The number was 9.999
7 but we rounded it off to 10
.0.
可以省略第二个参数。 例如,[0-9]{2,} 匹配至少两位 0~9 的数字。
“[0-9]{2,}” => The number was 9.999
7 but we rounded it off to 10
.0.
如果逗号也省略掉则表示重复固定的次数。 例如,[0-9]{3} 匹配3位数字
“[0-9]{3}” => The number was 9.999
7 but we rounded it off to 10.0.
2.重复区间:重复m~n次
{1} * {0,}
匹配左边字符出现>= 0次的,也就是任意次
例如,表达式 a* 匹配0或更多个以a开头的字符。表达式[a-z]* 匹配一行中所有以小写字母开头的字符串。
“[a-z]*” => The car parked in the garage
#21.
*字符和.字符搭配可以匹配所有的字符.*。
*和表示匹配空格的符号\s连起来用,如表达式\s*cat\s*匹配0或更多个空格开头和0或更多个空格结尾的cat字符串。
“\scat\s” => The fatcat
sat on the concat
enation. 注意第一个cat包括左右2边的空格
{2} + {1,}
+号匹配+号之前的字符出现 >=1 次。 例如表达式c.+t
匹配以首字母c开头以t结尾,中间跟着至少一个字符的字符串。
“c.+t” => The fat cat sat on the mat
. 注意,只返回cat sat on the mat
21个,cat
不会单独返回。
{3} ?可选字符 {0,1}、非贪婪模式(最多匹配一次)、默认是贪婪模式
表示左边字符出现0或1次的字符串
例如,表达式 [T]?he 匹配字符串 he 和 The。
“[T]he” => The
car is parked in the garage.
“[T]?he” => The
car is parked in the
garage. 注意是he不是the
3.没有单个量词的意思是“正好是m或n次”
X{n}|X{m}
X{m}(X{k})?其中m < n和k是的值n-m。
五、标志
用来丰富判断逻辑的标识符。
标志也叫模式修正符,因为它可以用来修改表达式的搜索结果。 这些标志可以任意的组合使用,它也是整个正则表达式的一部分。
使用时需要在标志前面分别加1个/
,注意是左斜。表示前面的字符|特征标群使用这个标志。
标志 | 描述 |
---|---|
i | 忽略大小写。 |
g | 全局搜索。 |
m | 多行修饰符:锚点元字符 ^ $ 工作范围在每行的起始。 |
1. 忽略大小写(Case Insensitive)
修饰语 i 用于忽略大小写。
例如,表达式 /The/gi 表示在全局搜索 The,在后面的 i 将其条件修改为忽略大小写,则变成搜索 the 和 The,g 表示全局搜索。
“The” => The
fat cat sat on the mat.
“/The/gi” => The
fat cat sat on the
mat.
2. 全局搜索(Global search)
默认只返回第一个。g用来返回全部结果,而不是第一个,
“/.(at)/” => The fat
cat sat on the mat.
“/.(at)/g” => The fat cat sat
on the mat
.
3. 多行修饰符(Multiline)
多行修饰符 m 常用于执行一个多行匹配。
像之前介绍的 (^,$) 用于检查格式是否是在待检测字符串的开头或结尾。但我们如果想要它在每行的开头和结尾生效,我们需要用到多行修饰符 m。
例如,表达式 /at(.)?$/gm 表示小写字符 a 后跟小写字符 t ,末尾可选除换行符外任意字符。根据 m 修饰符,现在表达式匹配每行的结尾。
六、回溯引用、反向引用(在模式内部引用前面匹配到的内容,类似于变量)
(\w)(\w)(\2)(\1)
( ) 特征标群,捕获括号(提取数据时,只会提取括号内的内容),分组
搜索后的操作:提取、替换、转换大小写
八、贪婪匹配与惰性匹配(Greedy vs lazy matching)
正则表达式默认采用贪婪匹配模式,在该模式下意味着会匹配尽可能长的子串。我们可以使用 ? 将贪婪匹配模式转化为惰性匹配模式。
“/(.at)/" => The fat cat sat on the mat
.
"/(.?at)/” => The fat
cat sat on the mat.
NPC:"那你看这样一道题:给你一些字符串,统计其末尾 e 的个数:
LeetCode
LeetCodeeee
LeetCodeee
"你:“看起来并不难,用(\w+)(e*)匹配,再取 group(2) 判断即可。”
Pattern pattern=Pattern.compile("(\\w+)(e*)");
Matcher matcher=pattern.matcher("LeetCode");
if(matcher.matches()) {
String group1=matcher.group(1);
String group2=matcher.group(2);
System.out.println("group1 = "+group1+", length = "+group1.length());
System.out.println("group2 = "+group2+", length = "+group2.length());
}
NPC:“你运行一下试试看。”
输出如下:
group1=LeetCode, length=8
group2=, length=0
你:"怎么会这样?我期望的结果是 group1 等于 LeetCod,group2 等于 e 才对啊!
"NPC:“这是因为 e 仍然属于 \w 能匹配的范畴,正则表达式默认会尽可能多地向后匹配,我们王国将其称之为 贪婪匹配。”
你:“贪婪匹配,听起来和贪心算法有异曲同工之妙。”
NPC:“没错,贪婪匹配和贪心算法原理是一致的。与之对应的匹配方式叫做 非贪婪匹配,非贪婪匹配 会在能匹配目标字符串的前提下,尽可能少的向后匹配。”
你:“那么,我要怎样指定匹配方式为非贪婪匹配呢?”
NPC:"也很简单,在需要非贪婪匹配的正则表达式后面加个 ? 即可表示非贪婪匹配。
Pattern pattern=Pattern.compile("(\\w+?)(e*)");
Matcher matcher=pattern.matcher("LeetCode");
if(matcher.matches()) {
String group1=matcher.group(1);
String group2=matcher.group(2);
System.out.println("group1 = "+group1+", length = "+group1.length());
System.out.println("group2 = "+group2+", length = "+group2.length());
}
运行程序,输出如下:
group1=LeetCod, length=7
group2=e, length=1
你:“这里也用的是?,我记得之前?表示的是匹配 0 次或者 1 次,两个符号不会混淆吗?”
NPC:“不会混淆的,你仔细想一想就能明白了,如果只有一个字符,那就不存在贪婪不贪婪的问题,如果匹配多次,那么表示非贪婪匹配的?前面必有一个标志匹配次数的符号。所以不会出现混淆。”
你:“最后一个问题,为什么这里没有匹配成 group1 等于 L,group2 等于 ee… 哦我明白了,如果这样匹配的话,字符串LeetCode就无法和正则表达式匹配起来。怪不得非贪婪匹配的定义是在能匹配目标字符串的前提下,尽可能少的向后匹配。”
NPC:“就是这个原理,看来你是真的完全明白了。”
九、example
1. 手机号
-
目前国内的手机号码是1(3/4/5/7/8)开头的 11 位数字,因此手机号码的正则可以分解为以下几部分:
以 1 开头:/^1/
第 2 位为3、4、5、7、8中的一个:/[34578]/ 或 /(3|4|5|7|8)/
剩余 3-11 位均为数字,并以数字结尾:/\d{9}$/ -
组合起来即为
/^1[34578]\d{9}$/
或/^1(3|4|5|7|8)\d{9}$/
,因为使用捕获(特征标群)括号存在性能损失,所以推荐使用第一种写法。
2. 电子邮件
标准的电子邮件组成为 <yourname>@<domain>.<extension><optional-extension>
- 每部分的格式标准为(进行了相应的简化,主要为展示如何书写正则):
yourname:任意英文字母(a-z/A-Z)、数字(0-9)、下划线(_)、英文句点(.)、连字符(-),长度大于 0
domain:任意英文字母(a-z/A-Z)、数字(0-9)、连字符(-),长度大于 0
extension:任意英文字母(a-z/A-Z),长度 2-8
optional-extension:"."开头,后面跟任意英文字母(a-z/A-Z),长度 2-8,可选 - 每部分的正则表达式为:
yourname:/[a-z\d._-]+/
domain:/[a-z\d-]+/
extension: /[a-z]{2,8}/
optional-extension:/(.[a-z]{2,8})?/ - 组合起来形成最后的正则表达式:
/^([a-z\d._-]+)@([a-z\d-]+)\.([a-z]{2,8})(\.[a-z]{2,8})?$/
为了增加可读性可以将每部分用"()"包起来,并不要忘记起始和结束符 ^$。
3.中文字符 [\u4e00-\u9fa5]
十、javaAPI
正则又叫模式,pattern
1.正则的对象pattern,判断是否匹配用matches(),提取用matcher
正则生成pattern对象
- 可以用来验证指定串是否符合。
Pattern pattern = Pattern.compile("[0-9]+");
System.out.println("123".matches("[0-9]+"));
System.out.println(pattern.matcher("123").matches());
- 可以用来提取指定串中的内容,matcher对象一个提取括号生成一个group,可以用循环。
Pattern pattern=Pattern.compile("Name:(\\w+)\\s*Age:(\\d{1,3})");
Matcher matcher=pattern.matcher("Name:Aurora Age:18");
if(matcher.matches()) {
String group1=matcher.group(1);
String group2=matcher.group(2);
System.out.println(group1);// 输出为 Aurora
System.out.println(group2);// 输出为 18
}
2.字符串直接调用调用matches
System.out.println(“Name:Aurora Age:18”.matches(“Name:\w+\s*Age:\d{1,3}”)); // 输出为 true
3.split
用字符类描述分隔符
System.out.println(Arrays.toString("二分,回溯,递归,分治".split("[,;\\s]
+")));
System.out.println(Arrays.toString("搜索;查找;旋转;遍历".split("[,;\\s]
+")));
System.out.println(Arrays.toString("数论 图论 逻辑 概率".split("[,;\\s]
+")));
[二分, 回溯, 递归, 分治]
[搜索, 查找, 旋转, 遍历]
[数论, 图论, 逻辑, 概率]
4.replaceall
System.out.println("二分,回溯,递归,分治".replaceAll("[,;\\s]+", ";"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("[,;\\s]+", ";"));
System.out.println("数论 图论 逻辑 概率".replaceAll("[,;\\s]+", ";"));
二分;回溯;递归;分治
搜索;查找;旋转;遍历
数论;图论;逻辑;概率
还可以回溯引用,$1就是匹配到的字符
System.out.println("二分,回溯,递归,分治".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("搜索;查找;旋转;遍历".replaceAll("([,;\\s]+)", "---$1---"));
System.out.println("数论 图论 逻辑 概率".replaceAll("([,;\\s]+)", "---$1---"));
二分---,---回溯---,---递归---,---分治
搜索---;---查找---;---旋转---;---遍历
数论------图论------逻辑------概率