首页 > 编程语言 >超详细 正则表达式【源码解析+代码例子+图】

超详细 正则表达式【源码解析+代码例子+图】

时间:2024-09-17 11:19:38浏览次数:3  
标签:匹配 String 正则表达式 matcher content 源码 Pattern regStr 解析

由于正则表达式这个东西比较抽象,我推荐大家先看原理部分。在看原理部分如果有的表达式看不懂可以去下面看表,元字符这些东西还是比较好理解的。大家可以把我写的代码复制到编译器上跑一下,这样会更容易理解。

一.基本介绍

正则表达式就是用某一种模式去匹配字符串,筛选我们想要的字符串的一种方法。

正则表达式在爬虫上有所应用,比如我们要爬取一个一个网页上的电话号码,但是网页上有很多中文、英文、时间等等,如果让我们自己写的话需要很长时间,但是如果我们使用正则表达式就可以快速解决。

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

相关文章

  • 解析Redisson 限流器源码
     工具类publicclassRedisUtils{  privatestaticfinalRedissonClientCLIENT=SpringUtils.getBean(RedissonClient.class);  /**  *限流  *  *@paramkey     限流key  *@paramrateType  限流类型  *@paramrate ......
  • 深入解析JSON:数据交换的通用语言
    目录1.引言2.JSON的起源3.JSON的结构4.JSON的优势5.JSON在软件开发中的应用5.1WebAPI5.2配置文件5.3数据存储6.使用JSON的最佳实践7.结论1.引言在当今的软件开发世界中,数据交换是应用程序之间通信的核心。随着WebAPI和微服务架构的兴起,一种轻量级的......
  • 基于微信美食菜谱点评小程序系统毕业设计 源代码作品源码成品
      博主介绍:黄菊华老师《Vue.js入门与商城开发实战》《微信小程序商城开发》图书作者,CSDN博客专家,在线教育专家,CSDN钻石讲师;专注大学生毕业设计教育和辅导。所有项目都配有从入门到精通的基础知识视频课程,学习后应对毕业设计答辩。项目配有对应开发文档、开题报告、任务书......
  • .NET源码的在线探索:source.dot.net网站深度解析
    一个在线的.NET源码查询网站为https://source.dot.net/。这个网站为开发者提供了便捷的.NET源码查询服务,无需从GitHub等代码托管平台下载整个源代码库,即可在线浏览和查询.NET框架及相关项目的源代码。以下是该网站的一些主要功能特性:在线查询:用户可以直接在网站上搜索特......
  • 深入底层源码,剖析AQS的来龙去脉!
    这里写目录标题回顾前缀知识一、Condition的概念二、Condition底层结构三、Condition源码解析3.1newCondition()3.2await()总结主要方法:回顾如果你还没熟悉AQS中的独占锁,可以先看这篇文章的前导篇。上一篇文章是以ReentrantLock里面的加锁、解锁源码进行分......
  • 汽车资讯网站|基于springboot+vue的汽车资讯网站(源码+数据库+文档)
    汽车资讯网站目录基于springboot+vue的汽车资讯网站一、前言二、系统设计三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取:博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专家博主,CSDN平台Java领......
  • 企业管理|基于springboot+vue的企业OA管理系统(源码+数据库+文档)
    企业管理目录基于springboot+vue的企业OA管理系统一、前言二、系统设计三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取:博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专家博主,CSDN平台Java领域......
  • 智慧宿舍平台|基于Springboot+vue的智慧宿舍系统(源码+数据库+文档)
    智慧宿舍系统目录基于Springboot+vue的智慧宿舍系统一、前言二、系统设计三、系统功能设计四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专家博主,CSDN平台Java领域......
  • 电子竞技信息交流平台|基于java的电子竞技信息交流平台系统小程序(源码+数据库+文档)
    电子竞技信息交流平台系统小程序目录基于java的电子竞技信息交流平台系统小程序一、前言二、系统设计三、系统功能设计四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取:博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计......
  • 课堂助手|微信课堂助手系统小程序(源码+数据库+文档)
    课堂助手|课堂助手系统小程序目录微信课堂助手系统小程序一、前言二、系统设计三、系统功能设计四、数据库设计 五、核心代码 六、论文参考七、最新计算机毕设选题推荐八、源码获取: 博主介绍:✌️大厂码农|毕设布道师,阿里云开发社区乘风者计划专家博主,CSDN平台......