由于正则表达式这个东西比较抽象,我推荐大家先看原理部分。在看原理部分如果有的表达式看不懂可以去下面看表,元字符这些东西还是比较好理解的。大家可以把我写的代码复制到编译器上跑一下,这样会更容易理解。
一.基本介绍
正则表达式就是用某一种模式去匹配字符串,筛选我们想要的字符串的一种方法。
正则表达式在爬虫上有所应用,比如我们要爬取一个一个网页上的电话号码,但是网页上有很多中文、英文、时间等等,如果让我们自己写的话需要很长时间,但是如果我们使用正则表达式就可以快速解决。
Java具有用于处理正则表达式的内置 API,我们本篇文章介绍的也是Java中的正则表达式。
在Java中使用正则表达式要调用:
import java.util.regex
regex -> regular expression,这是正则表达式的英文。
二.底层实现
这是网上很多文章都没有的部分,他们只说了分组会这么样,没有说捕获分组的原理。
使用正则表达式,我们要先创建一个正则表达式对象,里面填上我们设定的规则。
String regStr="\\d\\d\\d\\d";
Pattern pattern = Pattern.compile(regStr);
我们建立了一个模式对象pattern,接着我们会创建匹配器,匹配器会根据我们指定的规则进行筛选字符串:
Matcher matcher = pattern.matcher(content);
//这里的content就是我们要匹配的内容
创建好匹配器后我们可以开始匹配了:
while(matcher.find()){
System.out.println("数字:"+matcher.group(0));
}
这个时候我们就可以输出匹配成功的内容。
那么底层匹配的逻辑到底是什么呢?
matcher.find() 方法的执行过程:
1.根据指定的规则,定位符合规则的子字符串;
2.找到后将 子字符串的开始索引记录到 matcher 对象的属性 int[] groups
groups[0]上,将子字符串的结束索引+1记录到groups[1];
3.同时记录oldLast 的值子字符串结束索引+1,为下次执行find()方法做准备。
上面是find()方法的执行过程,我们可知会有一个专门的数组来存放匹配到内容的索引,这是捕获内容,那么matcher.group(0)是这么执行的呢?
这里就要看matcher.group(0)的源码了:
public String group(int group) {
if (first < 0)
throw new IllegalStateException("No match found");
if (group < 0 || group > groupCount())
throw new IndexOutOfBoundsException("No group " + group);
if ((groups[group*2] == -1) || (groups[group*2+1] == -1))
return null;
return getSubSequence(groups[group * 2], groups[group * 2 + 1]).toString();
}
上面的不看,直接看最后一行的return,我们给了0,group=0,其取的是groups[0]和groups[1]的值,刚好是我们上面捕获时存的下标的位置。
每次方法的形参group都取0,这样就可以保证每次取到的groups都是groups[0]和groups[1]了。
当下次find()方法执行的时候又会匹配到新的内容,将原来的0和1位置覆盖,填上新的索引。
例子:
String content="1145chunping1919" ;
String regStr="\\d\\d\\d\\d";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("数字:"+matcher.group(0));
}
可以看到groups中的0和1位置变成了匹配到第一个内容的索引(注意,此时只在while进行一次,再进行一次的话会发生变化)。
下面说一下分组
String content="1145chunping1919" ;
String regStr="(\\d\\d)(\\d\\d)";
上面regStr就是分组表示,这种再执行下面的操作:
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("数字:"+matcher.group(0));
}
输出的内容与没分组的变化不大,不同的是这里我们可以输出group(1),group(2)的内容。
下面是分组的底层逻辑:
分组后在find()这里先整体匹配,将整体的符合规则的内容开始和结束+1的索引存入groups[0]和groups[1]中,这里和没分组时一样的,但是下面的进行的是不一样的。
会根据我们给出的分组,将第一组开始的索引存入groups[2]中,结束索引+1存入groups[3];同样,第二组开始的索引存入groups[4],结束索引+1存入groups[5]中。依此类推,如果我们设置了更多的分组,会将更多组的下标存入groups中。
这时候在看matcher.group()的源码,当group=1时,取的是groups[2]和groups[3];当group=2时,取的是groups[4]和groups[5]。依此类推。
如果大家调用matcher.group(),括号中不填任何值,那么其会直接调用matcher.group(0),下面是源码:
public String group() {
return group(0);
}
以上就是捕获和分组的底层原理,理解了这个后面的就很好说了。
三.元字符
要想写正则表达式,就必须要学会元字符的用法。
1.转义字符
转义字符的符号是 \\ 。
为什么要用这个呢?当我们在检索一些特殊字符的时候,如果不用会检测不到。注意,在Java正则表达式中两个 \\ 等于一个 \ (直接写一个 \ 有其自己的用途,这里是为了区分)。
举个例子:
//这个是我们要匹配的字符串
String content="124$(";
//这个是我们的匹配规则
String regStr="(";
//正确写法
String regStr="\\(";
我们想要匹配content中的 '(' ,如果我们没有用转义字符,编译器会报错。
常见的要用转移字符的有下面几种:
. * + () $ /\ ? [] ^ {}
2.字符匹配符
符号 | 含义 | 示例 | 解释 |
[ ] | 可接受字符列表 | [abc] | a,b,c中任意的字符 |
[ ^ ] | 不可接受的字符列表 | [^abc] | 除abc之外的任意一个字符,包括数字和特殊字符 |
- | 连字符 | a-z | 任意小写字母 |
. | 匹配除 \n 以外的任何字符 | a.b | 以a开头,以b结尾,中的有一个任意字符 |
\\d | 匹配单个数字字符 | 无 | 无 |
\\D | 匹配单个非数字字符 | 无 | 无 |
\\w | 匹配单个数字、大小写字母字符 | 无 | 无 |
\\W | 匹配单个非数字、非大小写字母字符 | 无 | 无 |
补充:上面填无的地方后面都会有例子解释。
[] ^ - 应用
String regStr="[a-z]"; //匹配小写字母,区分大小写
String regStr="[A-Z]";
String regStr="(?i)[A-Z]"; //不区分大小写的话就加上(?i) 这里的i是 ignore - case(忽略)
String regStr="[^a-z]"; //不包含a-z
忽略大小写的另一种方法
Pattern pattern = Pattern.compile(regStr,Pattern.CASE_INSENSITIVE);
(.)可以匹配任意字符:
String content="a11c2";
String regStr=".";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.print(matcher.group(0));
}
//这个是输出的结果
a11c2
\\d和\\w的演示:
String content="a11c2";
String regStr="\\d";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.print(matcher.group(0));
}
//运行结果
112
String content="a11c2";
String regStr="\\d";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.print(matcher.group(0));
}
//运行结果
a11c2
以上就是字符匹配符的演示,还是比较简单的。
3.选择匹配符
选择匹配符的符号是:| ,用于匹配 (|)左右的字符。
String content="a11c2";
String regStr="a|b";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.print(matcher.group(0));
}
//运行结果
ac
这个很好理解,就是匹配 | 两边的字符,满足那个就匹配那个。
4.限定符
符号 | 含义 |
* | 指定字符重复0次或n次(零到多) |
+ | 指定字符重复1次或n次(1到多) |
? | 指定字符重复0次或1次(0或1) |
{n} | 只能输入n个字符 |
{n,} | 指定至少n个匹配 |
{n,m} | 指定至少n个但不多于m个匹配 |
//匹配1个或多个a
String regStr = "a+";
//匹配4个1
String regStr="1{4}";
//匹配两个数字
String regStr="\\d{2}";
//java贪婪匹配,尽量匹配多的,所以会优先匹配4个1
String regStr="1{3,4}";
限定符这个也是很好理解的,大家简单在编译器上试一下就学会了。
补充:这里的 ?还有另外的作用,可以将贪婪匹配改成非贪婪匹配。
我们知道Java正则表达式中是贪婪匹配的,就是尽可能多的匹配字符,如果我们使用了?那么就是改变这个情况。举个例子:
String content="114514";
String regStr="\\d+";
//正常我们使用上面这种写法会匹配:114514
//但是如果我们将上了?
String regStr="\\d+?";
//那么其每次就匹配1个字符,不会一下将114514全部匹配
5.定位符
符号 | 含义 |
^ | 指定起始字符 |
$ | 指定结束字符 |
\\b | 匹配目标字符串的边界 |
\\B | 匹配目标字符串的非边界 |
这些符号就比较抽象了,下面我们要例子来让大家清晰理解一下:
String content="123abc";
String regStr="^[0-9]+[a-z]*";
这里使用^来定位起始字符,可以看到我们规定了匹配的字符串必须是0-9也就是数字开头,且后面可以有(1到多个数字,因为有+),然后就是有0到多个小写字母。
这里说明一下,+说的范围包括其实字符,如果content的内容是"1abc"也是可以匹配的。
String content="123abc";
String regStr="^[0-9]+[a-z]*$";
$的用法与^大同小异,这里直接使用了两个(^和$)。
String content="regfwoeif gwgreg weforeg";
String regStr="reg\\b";
这里会匹配到"regfwoeif gwgreg weforeg"标红的reg。
\\b匹配某一个字符串最后的子串或者字符串中间空格处的最后的子串。
String content="regfwoeif gwg weregfo";
String regStr="reg\\B";
这里会匹配到"regfwoeif gwg weregfo"标红的reg。
\\B匹配某一个字符串开头的字串或中间出现的。
四.捕获分组和非捕获分组
1.捕获分组
常用的分组构造方法有两种:
第一种,非命名捕获,直接填入规则:
String regStr="(\\d\\d)(\\d\\d)";
第二种,命名捕获,语法就是?<组名>:
String regStr="(?<g1>\\d\\d)(?<g2>\\d\\d)";
第一种我们上面介绍了,这里只介绍第二种。
有了命名后,我们在分组的时候可以直接使用组名来进行取值:
System.out.println("找到第一组:"+matcher.group("g1"));
System.out.println("找到第一组:"+matcher.group(1));
//这两种写法是等效的
2.非捕获分组
非捕获分组常用的有三种构造形式。
这里直接举例子更加清晰:
String content="淳平吃饭aaa 淳平睡觉bb cc淳平学习";
第一种:(?:)
String regStr="淳平(?:吃饭|睡觉|学习)";
String regStr="淳平吃饭|淳平睡觉|淳平学习";
//这两种写法是等效的
第一种本质上与下面的那个写法没什么两样,就是下面写法的另一个版本。
输出的是:淳平吃饭 淳平睡觉 淳平学习
第二种:(?=)
String regStr="淳平(?=吃饭|睡觉|学习)";
第二种在匹配逻辑上与第一种没区别,只是在输出时会有差别,第二种只输出前面的内容。
像我们的例子,只会输出:淳平 淳平 淳平。
第三种:(?!)
String regStr="淳平(?!睡觉|学习)";
第三种相当于是一个取反操作,从输出结果也可以看出来:淳平吃饭。
3.反向应用
反向引用:在圆括号的内容被捕获后,可以在这个括号后面被使用。
内部反向引用用\\ ,外部反向引用用$。
这样说不够形象,下面举一个例子:
String content="1221 1111 1234";
String regStr="(\\d)(\\d)\\2\\1";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println(matcher.group(0));
}
上述例子其实就是找出回文数,大家可以看到在我们分组的括号后面有(\\2 \\1)这两个,\\2取的是第二组的值,\\1取的是第一组的值,这样取就可以保证是回文数了。
五.Pattern类与Matcher类
1.Pattern类
pattern 对象是一个正则表达式对象。Pattern 类没有公共构造方法。要创建一个 Pattern 对象,调用其公共静态方法,返回一个Pattern 对象。
常用的Pattern类方法:Pattern.matches(规则,内容);
String content="114514a";
String regStr="[1-9][0-9]{4,}";
System.out.println(Pattern.matches(regStr,content));
//会返回false
matches的整体匹配,只要一个不一样就会返回false。
2.Matcher类
Matcher 对象是对输入字符串进行解释和匹配的引擎。与Pattern类一样,Matcher也没有公共构造方法。你需要调用Pattern 对象的 matcher方法来获得一个Matcher对象。
Matcher类常用方法:
1.matches()
这个与Pattern类的方法用法相同,其实Pattern类的matches()源码也是调用Matcher类的matches():
public static boolean matches(String regex, CharSequence input) {
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(input);
return m.matches();
}
2.start()和end()
其可以返回分组的索引:
String content="hello jake tom helloabc hello";
String regStr="hello";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
while(matcher.find()){
System.out.println("------------");
System.out.println(matcher.start()+" "+matcher.end());
}
//运行结果
------------
0 5
------------
16 21
------------
25 30
注意,这里end反对的索引跟前面分析的一样,也是结束索引+1。
3.replaceAll()
将匹配到的字符串进行替换:
String content="hello jake tom helloabc hello";
String regStr="hello";
Pattern pattern = Pattern.compile(regStr);
Matcher matcher = pattern.matcher(content);
String newContent = matcher.replaceAll("你好");
System.out.println(newContent);
//运行结果
你好 jake tom 你好abc 你好
六.String类中使用正则表达式
1.replaceAll(String regx)
可以将匹配到的字符串给替换成另外一个,这个与Matcher类的那一个很像:
String content="hello jake tom helloabc hello";
String regStr="hello";
content=content.replaceAll(regStr,"你好");
//运行结果
你好 jake tom 你好abc 你好
2.matches(String regx)
这个Matcher类中也有一个一样的,用法参考就可以:
String content="1145141919810";
String regStr="114514\\d{7}";
System.out.println(content.matches(regStr));
//运行结果
true
3.split(String regx)
这个是字符串切割方法,我们可以根据自己的需要写出正则表达式,然后去匹配字符串,将符合条件的放入一个字符串数组中:
String content="1145#14-1919-810";
String regStr="#|-";
String[] split = content.split(regStr);
这里代码就可以将#和-给筛掉。
七.MySQL中使用正则表达式
1.使用
关键词是:regexp。
select 要查的列名 from 表名 where 列名 regexp '正则表达式';
//例子
select * from student where phone regexp '[^8]';
给大家提供一个题目练练手:1517. 查找拥有有效邮箱的用户
2.注意
在MySQL中使用正则表达式基础语法没什么区别,但是有一个点要注意了:在MySQL中正则表达式匹配的是字串,也就是只需要字串满足筛选条件,这个数据就能被筛出来。下面看个例子:
select * from student where name regexp '[^bc]';
假设上述是我们查询语句,我们有一个数据是abc,那么整个数据是可以匹配上的,因为其一个字串:'a'满足条件。
注意上方 '[^bc]' 这里,要加 [ ],
3.匹配字符类
[:alnum:] | 匹配字母与数字字符 |
[:digit:] | 匹配数字字符 |
[:alpha:] | 匹配字母字符 |
[:blank:] | 匹配空格与制表符 |
[:cntrl:] | 匹配控制字符 |
[:graph:] | 匹配可打印与可见字符 |
[:lower:] | 匹配小写字符 |
[:upper:] | 匹配大写字符 |
[:print:] | 匹配可打印字符 |
[:punct:] | 匹配标点字符 |
[:space:] | 匹配包含空格在内的任意空白字符 |
4.like与regexp的区别
like:
将字符串整个匹配,真个字符串必须完全满足我们给出的模式。
regexp:
在整个字符串中查找字串,只要整个串中的字串存在于我们设定的模式相同的字串,那么就匹配成功。
标签:匹配,String,正则表达式,matcher,content,源码,Pattern,regStr,解析 From: https://blog.csdn.net/lllsure/article/details/142208782