# # # 正则
正则很6很强大。最为神奇的是不管你大学选择那门计算机语言,都没有关于正则表达式的课程给你修,你只能仰望大师们写了一串外文字符串替代你的通篇if else代码来做一些数据校验。既然喜欢那就学呗!接下来整理一下正则,看过之后希望可以帮到你,不能说你可以写出多高大上的正则表达式,但再不济看懂别人写的正则,那也不错了。
正则表达式在很多语言中都可以使用,无论是前端的JavaScript还是后端的Java。都有提供相应的接口 \ 函数支持正则表达式。
1.元字符
万物皆有缘,正则也如此,元字符是构造正则表达式的一种基本元素。
常用的元字符
元字符 | 说明 |
. | 匹配除换行符以外的任意字符 |
\w | 匹配字母或者数字或下划线或汉字 |
\s | 匹配任意的空白符 |
\d | 匹配数字 |
\b | 匹配单词的开始或结束 |
^ | 匹配字符串的开始 |
$ | 匹配字符串的结束 |
下面我们来写一些简单的正则表达式:
匹配有abc开头的字符串: \babc或^abc
匹配8位数字的QQ号码: ^\d\d\d\d\d\d\d\d$
匹配1开头11位数字的手机号码: ^\d\d\d\d\d\d\d\d\d$
2.重复限定符
可以看的出以上代码并不简洁好多重复,下面说一下重复限定符,把重复部分用合适的限定符代替,
语法 | 说明 |
* | 重复零次或更多次 |
+ | 重复一次或更多次 |
? | 重复零次或一次 |
{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*$
3.分组
如果你想要ab同时限定的话可以用小括号()来做分组,括号中的内容作为一个整体。
譬如:匹配字符串中包含0到多个ab开头: ^(ab)*
4.转义
上面说分组用小括号,如果要匹配的字符串中本身带有小括号,那不就冲突了吗?难道就没办法了吗? No No No
针对这种情况,正则提供了一个名叫转义的方式,就是把元字符、限定字符、关键字转义长普通字符,方法就是要在转义字符前面加个斜杠。
譬如:要匹配以(ab)开头: ^(\(ab\))*
5.条件或
在正则中并列的条件也就是“或”,正则用符号 | 来表示或,也叫做分支条件,当满足正则里的分支条件的任何一种条件时,都会当成是匹配成功。
匹配一个联通号码:^(130|131|132|155|156|185|186|145|176)\d{8}$
6.区间
正则提供一个元字符中括号[]来表示区间条件。
限定0到9可以写成[0-9]
限定A-Z写成[A-Z]
限定某些数字[166]
上面的联通号码我们可以改成这样: ^((13[0-2])|(15[56])|(18[5-6])|145|176)\d{8}$
7.零宽断言
断言:用我们自己的话来理解就是“我断定什么”,在正则中,是指正则可以指明在指定的内容的前面或者后面会出现满足指定规则的内容。
零宽:就是没有宽度,在正则中断言只是匹配位置不占字符,就是说匹配结果里是不会返回断言本身。
我们来举个栗子:
"<span class="read-cound">阅读数:666</span>"
上面是用爬虫抓取csdn里的文章的阅读量,可以知道的是“666”这是一个变量,要想用正则怎么匹配?
下面唠一下几种类型的断言:
正向先行断言(正前瞻)
语法:(?=pattern)
作用:匹配pattern表达式的前面内容,不返回本身。
刚才那个栗子要取到阅读量在正则表达式中意味着要能匹配到'</span>'前面是数字内容,(?=</span>)就可以匹配到前面是内容了。
匹配所有内容:
1 String reg=".+(?=</span>)"; 2 3 String test = "<span class=\"read-count\">阅读数:666</span>"; 4 Pattern pattern = Pattern.compile(reg); 5 Matcher mc= pattern.matcher(test); 6 while(mc.find()){ 7 System.out.println("匹配结果:") 8 System.out.println(mc.group()); 9 } 10 11 //匹配结果: 12 //<span class="read-count">阅读数:666
老哥我只要前面的数字呀,那也简单,匹配数字\d,这样写
1String reg="\\d+(?=</span>)"; 2String test = "<span class=\"read-count\">阅读数:666</span>"; 3Pattern pattern = Pattern.compile(reg); 4Matcher mc= pattern.matcher(test); 5while(mc.find()){ 6 System.out.println(mc.group()); 7} 8//匹配结果: 9//666
正向后行断言(正后顾)
语法:(?<=pattern)
作用:匹配 pattern 表达式的后面的内容,不返回本身。
把上面的栗子再煮一遍:
1//(?<=<span class="read-count">阅读数:)\d+ 2String reg="(?<=<span class=\"read-count\">阅读数:)\\d+"; 3 4String test = "<span class=\"read-count\">阅读数:666</span>"; 5Pattern pattern = Pattern.compile(reg); 6Matcher mc= pattern.matcher(test); 7 while(mc.find()){ 8 System.out.println(mc.group()); 9 } 10//匹配结果: 11//666
可以收工了!
负向先行断言(负前瞻)
语法:(?!pattern)
作用:匹配非 pattern 表达式的前面内容,不返回本身。
举个栗子:"我是小可爱,小可爱是我!"
要找到“是我”前面的小可爱
用正则这样写:小可爱(?!是我)
负向后行断言(负后顾)
语法:(?<!pattern)
作用:匹配非 pattern 表达式的后面内容,不返回本身。
8.捕获和非捕获
单纯说到捕获,他的意思是匹配表达式,但捕获通常和分组联系在一起,也就是“捕获组”。
捕获组:匹配子表达式的内容,把匹配结果保存到内存中中数字编号或显示命名的组里,以深度优先进行编号,之后可以通过序号或名称来使用这些匹配结果。
根据命名方式的不同可以分为两种组:
数字编号捕获:
语法:(exp)
解释:从表达式左侧开始,每出现一个左括号和它对应的右括号之间的内容为一个分组,在分组中,第 0 组为整个表达式,第一组开始为分组。
比如固定电话:010-88943223
他的正则表达式为:(0\d{1})-(\d{8})
按照左括号的顺序,这个表达式有如下分组:
序号 | 编号 | 分组 | 内容 |
0 | 0 | (0\d{2})-(\d{8}) | 010-88943223 |
1 | 1 | (0\d{1}) | 010 |
2 | 2 | (\d{8}) | 88943223 |
用Java验证一下:
1 String test = "010-88943223"; 2 String reg="(0\\d{2})-(\\d{8})"; 3 Pattern pattern = Pattern.compile(reg); 4 Matcher mc= pattern.matcher(test); 5 if(mc.find()){ 6 System.out.println("分组的个数有:"+mc.groupCount()); 7 for(int i=0;i<=mc.groupCount();i++){ 8 System.out.println("第"+i+"个分组为:"+mc.group(i)); 9 } 10 }
输出结果:
1分组的个数有:2
2第0个分组为:020-85653333
3第1个分组为:020
4第2个分组为:85653333
可见,分组个数是2,但是因为第0个为整个表达式本身,因此也一起输出了。
命名编号捕获组
语法:(?<name>exp)
解释:分组的命名由表达式中的 name 指定
比如区号也可以这样写:(?<quhao>\0\d{1})-(?<haoma>\d{8}),按照左括号的顺序,这个表达式有如下分组:
序号 | 名称 | 分组 | 内容 |
0 | 0 | (0\d{1})-(\d{8}) | 010-88943223 |
1 | quhao | (0\d{1}) | 010 |
2 | haoma | (\d{8}) | 88943223 |
用代码来验证一下:
1 String test = "020-85653333"; 2 String reg="(?<quhao>0\\d{2})-(?<haoma>\\d{8})"; 3 Pattern pattern = Pattern.compile(reg); 4 Matcher mc= pattern.matcher(test); 5 if(mc.find()){ 6 System.out.println("分组的个数有:"+mc.groupCount()); 7 System.out.println(mc.group("quhao")); 8 System.out.println(mc.group("haoma")); 9 }
输出结果:
1 分组的个数有:2
2 分组名称为:quhao,匹配内容为:010
3 分组名称为:haoma,匹配内容为:88943223
非捕获组
语法:(?:exp)
解释:和捕获组刚好相反,它用来标识那些不需要捕获的分组,说的通俗一点,就是你可以根据需要去保存你的分组。
序号 | 编号 | 分组 | 内容 |
0 | 0 | (0\d{1})-(\d{8}) |
010-88943223
|
1 | 1 | (\d{8}) |
88943223
|
验证一下:
1 String test = "020-85653333"; 2 String reg="(?:0\\d{2})-(\\d{8})"; 3 Pattern pattern = Pattern.compile(reg); 4 Matcher mc= pattern.matcher(test); 5 if(mc.find()){ 6 System.out.println("分组的个数有:"+mc.groupCount()); 7 for(int i=0;i<=mc.groupCount();i++){ 8 System.out.println("第"+i+"个分组为:"+mc.group(i)); 9 } 10 }
输出结果:
1 分组的个数有:1
2 第0个分组为:010-88943223
3 第1个分组为:88943223
9.反向引用
捕获会返回一个捕获组,这个分组是保存在内存中,不仅可以在正则表达式外部通过程序进行引用,也可以在正则表达式内部进行引用,这种引用方式就是反向引用。
根据捕获组的命名规则,反向引用可分为:
·数字编号组反向引用:\k 或 \number
·命名编号组反向引用:\k 或 \'name'
这样基本上算是已经说完了,但是我知道你还是不懂。
捕获组通常是和反向引用一起使用的,上面说到捕获组是匹配子表达式的内容按序号或者命名保存起来以便使用。划重点“内容” 和 “使用”!!!
这里的内容说的是匹配结果,而不是子表达式本身,这里是使用它的作用主要是用来查找一些重复的内容或者做替换指定字符。
如:要查找一串字母" aabbbbgbddesddfiid "里成对的字母。
思路:
1、匹配到一个字母
2、匹配第下一个字母,检查是否和上一个字母是否一样(怎么记住上一个字母?利用捕获把上一个匹配成功的内容用来作为本次匹配的条件)
3、 如果一样,则匹配成功,否则失败
首先匹配一个字母:\w,我们需要做成分组才能捕获,因此写成这样:(\w)
那这个表达式就有一个捕获组:(\w)
然后我们要用这个捕获组作为条件,那就可以:(\w)\1这样就大功告成了
可能有人不明白了,\1 是什么意思呢?
还记得捕获组有两种命名方式吗,一种是是根据捕获分组顺序命名,一种是自定义命名来作为捕获组的命名在默认情况下都是以数字来命名,而且数字命名的顺序是从 1 开始的。
因此要引用第一个捕获组,根据反向引用的数字命名规则 就需要 \k<1>或者\1,当然,通常都是是后者。
我们来测试一下:
1 String test = "aabbbbgbddesddfiid";
2 Pattern pattern = Pattern.compile("(\\w)\\1");
3 Matcher mc= pattern.matcher(test);
4 while(mc.find()){
5 System.out.println(mc.group());
6
7 }
输出结果:
1 aa
2 bb
3 bb
4 dd
5 dd
6 ii
10.贪婪和非贪婪
贪婪匹配:当正则表达式中包含能接受重复的限定符时,通常的行为是(在使整个表达式能得到匹配的前提下)匹配尽可能多的字符,这匹配方式叫做贪婪匹配。
特性:一次性读入整个字符串进行匹配,每当不匹配就舍弃最右边一个字符,继续匹配,依次匹配和舍弃(这种匹配 - 舍弃的方式也叫做回溯),直到匹配成功或者把整个字符串舍弃完为止,因此它是一种最大化的数据返回,能多不会少。
前面我们讲过重复限定符,其实这些限定符就是贪婪量词,比如表达式:\d{3,6}
用来匹配3到6位数字,在这种情况下,它是一种贪婪模式的匹配,也就是假如字符串里有6个个数字可以匹配,那它就是全部匹配到。
如:
1 String reg="\\d{3,6}";
2 String test="61762828 176 2991 871";
3 System.out.println("文本:"+test);
4 System.out.println("贪婪模式:"+reg);
5 Pattern p1 =Pattern.compile(reg);
6 Matcher m1 = p1.matcher(test);
7 while(m1.find()){
8 System.out.println("匹配结果:"+m1.group(0));
9 }
输出结果:
1 文本:61762828 176 2991 44 871
2 贪婪模式:\d{3,6}
3 匹配结果:617628
4 匹配结果:176
5 匹配结果:2991
6 匹配结果:871
多个贪婪词凑在一起会怎样?
多个贪婪在一起时,如果字符串能满足他们各自最大程度的匹配时,就互不干扰,但如果不能满足时,会根据深度优先原则,也就是从左到右的每一个贪婪量词,优先最大数量的满足,剩余再分配下一个量词匹配。
1 String reg="(\\d{1,2})(\\d{3,4})";
2 String test="61762828 176 2991 87321";
3 System.out.println("文本:"+test);
4 System.out.println("贪婪模式:"+reg);
5 Pattern p1 =Pattern.compile(reg);
6 Matcher m1 = p1.matcher(test);
7 while(m1.find()){
8 System.out.println("匹配结果:"+m1.group(0));
9 }
输出结果:
1 文本:61762828 176 2991 87321
2 贪婪模式:(\d{1,2})(\d{3,4})
3 匹配结果:617628
4 匹配结果:2991
5 匹配结果: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次以上,但尽可能少重复 |
1 String reg="(\\d{1,2}?)(\\d{3,4})";
2 String test="61762828 176 2991 87321";
3 System.out.println("文本:"+test);
4 System.out.println("贪婪模式:"+reg);
5 Pattern p1 =Pattern.compile(reg);
6 Matcher m1 = p1.matcher(test);
7 while(m1.find()){
8 System.out.println("匹配结果:"+m1.group(0));
9 }
输出结果:
1 文本:61762828 176 2991 87321
2 贪婪模式:(\d{1,2}?)(\d{3,4})
3 匹配结果:61762
4 匹配结果:2991
5 匹配结果:87321
解答:
-
“61762” 是左边的懒惰匹配出 6,右边的贪婪匹配出 1762
-
"2991" 是左边的懒惰匹配出 2,右边的贪婪匹配出 991
-
"87321" 左边的懒惰匹配出 8,右边的贪婪匹配出 7321
11.反义
元字符 | 解释 |
\W | 匹配任意不是字母,数字,下划线,汉字的字符 |
\S | 匹配任意不是空白符的字符 |
\D | 匹配任意非数字的字符 |
\B | 匹配不是单词开头或结束的位置 |
[^x] | 匹配除了x以外的任意字符 |
[^aeiou] | 匹配除了aeiou这几个字母以外的任意字符 |
我知道的基本就这么多了,经常研究和使用才能领悟它的博大精深。接下来我们一起学习研究!加油!