java 正则表达式优化
1,什么是正则表达式
正则表达式使用一些特定的元字符来检索、匹配以及替换符合规则的字符串。
构造正则表达式语法的元字符,由普通字符、标准字符、限定字符(量词)、定位字符(边界字符)组成
普通字符
字母[a-zA-Z]、数字[0-9]、下划线[-]、汉字、标点符号等。比如,regex=[a-z]匹配从a到z,26个字母的任意一个。
标准字符
能够与“多种普通字符”匹配的简单表达式。比如,\d、\w、\s等,匹配数字0到9的任意数字可以用普通字符实现regex=[0-9],也可以用标准字符实现regex=\d。
限定字符(量词)
用于表示匹配的字符数量。比如,*、+、?、[n}等,匹配任意1到3位的数字可以使用regex=\d{1,3}表示。
定位字符(边界字符)
“零宽”,标记匹配符合某种条件的位置。比如,$、^等字符,匹配以Hello开头的字符串可以使用regex=^Hello表示。
2. 正则表达式引擎
正则表达式是一个用正则符号写出的公式,程序对这个公式进行语法分析,建立一个语法分析树,再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机,也叫状态自动机),用于字符匹配。
而这里的正则表达式引擎就是一套核心算法,用于建立状态机。
目前实现正则表达式引擎的方式有两种:DFA 自动机(Deterministic Final Automata 确定有限状态自动机)和 NFA 自动机(Non deterministic Finite Automaton 非确定有限状态自动机)。
对比来看,构造 DFA 自动机的代价远大于 NFA 自动机,但 DFA 自动机的执行效率高于NFA 自动机。
假设一个字符串的长度是 n,如果用 DFA 自动机作为正则表达式引擎,则匹配的时间复杂度为 O(n);如果用 NFA 自动机作为正则表达式引擎,由于 NFA 自动机在匹配过程中存在大量的分支和回溯,假设 NFA 的状态数为 s,则该匹配算法的时间复杂度为 O(ns)。
NFA 自动机的优势是支持更多功能。例如,捕获 group、环视、占有优先量词等高级功能。这些功能都是基于子表达式独立进行匹配,因此在编程语言里,使用的正则表达式库都是基于 NFA 实现的。
那么 NFA 自动机到底是怎么进行匹配的呢?我以下面的字符和表达式来举例说明。
text=“aabcab”regex=“bc”
NFA 自动机会读取正则表达式的每一个字符,拿去和目标字符串匹配,匹配成功就换正则表达式的下一个字符,反之就继续和目标字符串的下一个字符进行匹配。
分解一下过程。
首先,读取正则表达式的第一个匹配符和字符串的第一个字符进行比较,b 对 a,不匹配;继续换字符串的下一个字符,也是 a,不匹配;继续换下一个,是 b,匹配。
然后,同理,读取正则表达式的第二个匹配符和字符串的第四个字符进行比较,c 对 c,匹配;继续读取正则表达式的下一个字符,然而后面已经没有可匹配的字符了,结束。
这就是 NFA 自动机的匹配过程,虽然在实际应用中,碰到的正则表达式都要比这复杂,但匹配方法是一样的。
NFA 自动机的回溯
用 NFA 自动机实现的比较复杂的正则表达式,在匹配过程中经常会引起回溯问题。大量的
回溯会长时间地占用 CPU,从而带来系统性能开销。我来举例说明。
text=“abbc”regex=“ab{1,3}c”
这个例子,匹配目的比较简单。匹配以 a 开头,以 c 结尾,中间有 1-3 个 b 字符的字符串。
NFA 自动机对其解析的过程是这样的:
首先,读取正则表达式第一个匹配符 a 和字符串第一个字符 a 进行比较,a 对 a,匹配。
然后,读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符 b 进行比较,匹配。但因为 b{1,3} 表示 1-3 个 b 字符串,NFA 自动机又具有贪婪特性,所以此时不会继续读取正则表达式的下一个匹配符,而是依旧使用 b{1,3} 和字符串的第三个字符 b 进行比较,结果还是匹配。
接着继续使用 b{1,3} 和字符串的第四个字符 c 进行比较,发现不匹配了,此时就会发生回溯,已经读取的字符串第四个字符 c 将被吐出去,指针回到第三个字符 b 的位置。
那么发生回溯以后,匹配过程怎么继续呢?程序会读取正则表达式的下一个匹配符 c,和字符串中的第四个字符 c 进行比较,结果匹配,结束。
如何避免回溯问题?
既然回溯会给系统带来性能开销,那如何应对呢?如果你有仔细看上面那个案例的话,
你会发现 NFA 自动机的贪婪特性就是导火索,这和正则表达式的匹配模式息息相关.
1. 贪婪模式(Greedy)
顾名思义,就是在数量匹配中,如果单独使用 +、 ? 、* 或{min,max} 等量词,正则表达式会匹配尽可能多的内容。
例如,上边那个例子:
text=“abbc”regex=“ab{1,3}c”
就是在贪婪模式下,NFA 自动机读取了最大的匹配范围,即匹配 3 个 b 字符。匹配发生了一次失败,就引起了一次回溯。如果匹配结果是“abbbc”,就会匹配成功。
2. 懒惰模式(Reluctant)
在该模式下,正则表达式会尽可能少地重复匹配字符。如果匹配成功,它会继续匹配剩余的字符串。
例如,在上面例子的字符后面加一个“?”,就可以开启懒惰模式。
text=“abc”regex=“ab{1,3}?c”
匹配结果是“abc”,该模式下 NFA 自动机首先选择最小的匹配范围,即匹配 1 个 b 字符,因此就避免了回溯问题。
3. 独占模式(Possessive)
同贪婪模式一样,独占模式一样会最大限度地匹配更多内容;不同的是,在独占模式下,匹配失败就会结束匹配,不会发生回溯问题。
还是上边的例子,在字符后面加一个“+”,就可以开启独占模式。
text=“abbc”regex=“ab{1,3}+bc”
结果是不匹配,结束匹配,不会发生回溯问题。
避免回溯的方法就是:使用懒惰模式和独占模式。
3,正则表达式的优化
1. 少用贪婪模式,多用独占模式
贪婪模式会引起回溯问题,我们可以使用独占模式来避免回溯。
2. 减少分支选择
分支选择类型“(X|Y|Z)”的正则表达式会降低性能,我们在开发的时候要尽量减少使用。
如果一定要用,我们可以通过以下几种方式来优化:
首先,我们需要考虑选择的顺序,将比较常用的选择项放在前面,使它们可以较快地被匹配;
其次,我们可以尝试提取共用模式,例如,将“(abcd|abef)”替换为“ab(cd|ef)”,后者匹配速度较快,因为 NFA 自动机会尝试匹配 ab,如果没有找到,就不会再尝试任何选项;
最后,如果是简单的分支选择类型,我们可以用三次 index 代替“(X|Y|Z)”,如果测试的话,你就会发现三次 index 的效率要比“(X|Y|Z)”高出一些。
3. 减少捕获嵌套 (?:exp)
捕获组是指把正则表达式中,子表达式匹配的内容保存到以数字编号或显式命名的数组中,方便后面引用。一般一个 () 就是一个捕获组,捕获组可以进行嵌套。
非捕获组则是指参与匹配却不进行分组编号的捕获组,其表达式一般由(?:exp)组成。
在正则表达式中,每个捕获组都有一个编号,编号 0 代表整个匹配到的内容。
例子:
public static void main(String args[]) {String text = "<input high=\"20\" weight=\"70\">test</input>";String reg = "(<input.*?>)(.*?)(</input>)";Pattern p = Pattern.compile(reg);Matcher m = p.matcher(text);while (m.find()) {System.out.println(m.group(0));System.out.println(m.group(1));System.out.println(m.group(2));System.out.println(m.group(3));}
}
输出:
<input high=\"20\" weight=\"70\">test</input>
<input high=\"20\" weight=\"70\">
test
</input>
如果你并不需要获取某一个分组内的文本,那么就使用非捕获分组。例如,使用“(?:X)”代替“(X)”
public static void main(String args[]) {String text = "<input high=\"20\" weight=\"70\">test</input>";String reg="(?:<input.*?>)(.*?)(?:</input>)";Pattern p = Pattern.compile(reg);Matcher m = p.matcher(text);while (m.find()) {System.out.println(m.group(0));System.out.println(m.group(1));}
}
输出:
<input high=\"20\" weight=\"70\">test</input>
test
综上:减少不需要获取的分组,可以提高正则表达式的性能。
总结
正则表达式虽然小,却有着强大的匹配功能。我们经常用到它,比如,注册页面手机号或邮箱的校验。
但很多时候,我们又会因为它小而忽略它的使用规则,测试用例中又没有覆盖到一些特殊用例,导致上线就中招的情况发生。
如果要使用正则表达式,要在做好性能排查的前提下;如果不能,那么正则表达式能不用就不用,以此避免造成更多的性能问题。
如果要使用正则表达式,养成使用独占或者懒惰模式的习惯。
相关文章:
java 正则表达式优化
1,什么是正则表达式 正则表达式使用一些特定的元字符来检索、匹配以及替换符合规则的字符串。 构造正则表达式语法的元字符,由普通字符、标准字符、限定字符(量词)、定位字符(边界字符)组成 普通字符 字母[…...
AD(Altium Designer)更换PCB文件的器件封装
一、确定是否拥有想换的器件PCB封装 1.1 打开现有的原理图 1.2 确定是否拥有想换的器件PCB文件 1.2.1 如果有 按照1.3进行切换器件PCB封装 1.2.2 如果没有 按照如下链接进行添加 AD(Altium Designer)已有封装库的基础上添加器件封装-CSDN博客https://blog.csdn.net/XU15…...
【文献研究】含硼钢中BN表面偏析对可镀性的影响
《B 添加钢的溶融 Zn めっき性に及ぼす BN 表面析出の影響》由JFE公司田原大輔等人撰写。研究聚焦 B 添加钢在低露点退火时 BN 形成对镀锌性的影响,对汽车用高强度钢镀锌工艺优化意义重大。通过多组对比实验,结合多种分析手段,明确了相关因素…...
React学习-css
W3Schools Tryit Editor CSS 教程 CSS 规则由两个主要的部分构成:选择器,以及一条或多条声明: p { /* 这是个注释 */ color:red; text-align:center; }选择器 CSS Id: #para1{ text-align:center; color:red; } Class: .center {text-align:center;} p…...
AIP-215 API特定proto
编号215原文链接AIP-215: API-specific protos状态批准创建日期2018-10-01更新日期2018-10-01 API通常使用API特定proto定义,偶尔依赖通用组件。保持API相互隔离可以避免版本问题和客户端库打包问题。 指南 所有特定于某个API的protos 必须 位于带有主版本号的包…...
C++ 获取一整行(一行)字符串并转换为数字
代码很简单,主要是自己总是忘记,记录一下: #include <iostream> #include <cstdlib> #include <cstring>#include <string> #include <vector> #include <sstream>using namespace std;void print_int_…...
数据分析-Excel-学习笔记Day1
Day1 复现报表聚合函数:日期联动快速定位区域SUMIF函数SUMIFS函数环比、同比计算IFERROR函数混合引用单元格格式总结汇报 拿到一个Excel表格,首先要看这个表格的构成(包含了哪些数据),几行几列,每一列的名称…...
树莓派PICO 设备烧录成cmsis dap
文章目录 1. 实际操作2. IO连接 1. 实际操作 2. IO连接...
【数据结构】图的存储
目录 邻接矩阵 表示方法 代码定义 结构特点与度的信息 邻接表 表示方法 代码定义 结构特点与度的信息 十字链表 表示方法 第二步,将顶点x的firstIn域与所有headvex域为x的弧连起来。 结构特点与度的信息 邻接多重表 表示方法 结构特点与度的信息 图…...
如何解决uniapp打包安卓只出现功能栏而无数据的问题
如何解决uniapp打包安卓只出现功能栏而无数据的问题 经验来自:关于Vue3中调试APP触发异常:exception:white screen cause create instanceContext failed,check js stack -> at useStore (app-service.js:2309:15)解决方案 - 甲辰哥来帮你算命 - 博客…...
kotlin,数字滚动选择
用国内的通义灵码和codegeex都没有弄出来,最后只得用墙外的chatgpt才弄出一个满意的。kotlin真的有点难,好在有AI,让学习没这难了。 package com.example.mynumsetimport android.os.Bundle import androidx.activity.ComponentActivity imp…...
【4】搭建k8s集群系列(二进制部署)之安装master节点组件(kube-apiserver)
一、下载k8s二进制文件 下载地址: https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG -1.20.md 注:打开链接你会发现里面有很多包,下载一个 server 包就够了,包含了 Master 和 Worker Node 二进制文件。…...
每日c/c++题 备战蓝桥杯(小球反弹)[镜像思路求解,最小公倍数]
思路: 错解:对于这道题而言,有的同学会选择用计算每次碰撞的坐标,直到坐标等于原点的方法来做,但这种方法实现起来比较繁琐,并且由于碰撞点的坐标有可能是浮点数,而浮点数会丢失精度࿰…...
新潮透明液体水珠水滴失真故障扭曲折射特效海报字体标题设计ps样机动作素材 Bubble Photoshop Templates
只需单击几下即可创建引人注目的视觉效果!您需要做的就是将您的文本或图像放入智能对象中并应用作。 包中包含: 15 个静态 Photoshop 模板(PS 2019 及更高版本) 01-05 垂直布局 (22504000)06-10 水平布局…...
谷歌洽谈租赁英伟达AI服务器:算力争夺战再升级
近日,全球科技巨头谷歌被曝正与英伟达(NVIDIA)洽谈租赁AI服务器,这一动态引发了行业广泛关注。若合作达成,谷歌将借助英伟达的高性能GPU进一步强化其AI算力储备,同时也折射出当前AI竞赛中算力资源的战略重要…...
从零开始玩python--python版植物大战僵尸来袭
大家好呀,小伙伴们!今天要给大家介绍一个超有趣的Python项目 - 用pygame制作植物大战僵尸游戏的进阶版本。相信不少小伙伴都玩过这款经典游戏,今天我们就用Python来实现它,让编程学习变得更加有趣!🌟 一、…...
深入理解MySQL:核心特性、优化与实践指南
MySQL是一个开源的关系型数据库管理系统(RDBMS),由瑞典MySQL AB公司开发,目前属于Oracle公司。它是目前世界上最流行的开源数据库之一,广泛应用于各种规模的Web应用和企业系统中。 目录 一、核心特点 关系型数据库: 开源免费&am…...
Visual Studio Code SSH 连接超时对策( keep SSH alive)
文章目录 问题解决方法一:配置服务端关于ClientAliveInterval和ClientAliveCountMax1、打开终端,打开SSH配置文件:输入以下命令:2、打开配置文件后,添加以下内容:3、添加后,Esc按 <Enter>…...
【C语言入门】由浅入深学习指针 【第二期】
目录 1. 指针变量为什么要有类型? 2. 野指针 2.1 未初始化导致的野指针 2.2 指针越界导致的野指针 2.3 如何规避野指针 3. 指针运算 3.1 指针加减整数 3.2 指针减指针 3.3 指针的关系运算 4. 二级指针 5. 指针数组 5.1 如何使用指针数组模拟二维数组 上…...
关于Ubuntu系统的远程控制及文件传输
目录 1. 网络配置1.1 虚拟机Ubuntu网络配置1.2树莓派网络配置 2. 远程终端登录3. FTP文件传输4. 安装Xming和PuTTY5. 使用X11转发6. 安装和使用VNC思考题解答参考资料 1. 网络配置 1.1 虚拟机Ubuntu网络配置 将虚拟机的网络连接设置为“桥接模式”,这样虚拟机的网…...
IS-IS-单区域的配置
一、IS-IS的概念 IS-IS(Intermediate System to Intermediate System,中间系统到中间系统)是一种链路状态路由协议,最初设计用于OSI(Open Systems Interconnection)参考模型的网络层(CL…...
Flask使用MySQL数据库通过Flask-SQLAlchemy 迁移数据库,实际更新文件,但是提示没有检测到数据更新。
本地写了一个model的用户类,数据库连接信息正确,执行下面2条命令进行数据库迁移。 flask db migrate 生成迁移文件 flask db upgrade 执行迁移文件的升级 发现执行完后:提示没有检测到数据的更新 PS C:\Users\mu> flask db migrate IN…...
React DndKit 实现类似slack 类别、频道拖动调整位置功能
一周调试终于实现了类 slack 类别、频道拖动调整位置功能。 历经四个版本迭代。 实现了类似slack 类别、频道拖动调整功能 从vue->react ;更喜欢React的生态及编程风格,新项目用React来重构了。 1.zustand全局状态 2.DndKit 拖动 功能视频&…...
OpenCV 图形API(14)用于执行矩阵(或图像)与一个标量值的逐元素乘法操作函数mulC()
操作系统:ubuntu22.04 OpenCV版本:OpenCV4.9 IDE:Visual Studio Code 编程语言:C11 描述 将矩阵与标量相乘。 mulC 函数将给定矩阵 src 的每个元素乘以一个给定的标量值: dst ( I ) saturate ( src1 ( I ) ⋅ multiplier ) \…...
flutter provider状态管理使用
在 Flutter 中,Provider 是一个轻量级且易于使用的状态管理工具,它基于 InheritedWidget,并提供了一种高效的方式来管理和共享应用中的状态。相比其他复杂的状态管理方案(如 Bloc 或 Riverpod),Provider 更…...
1. 标准库的强依赖(核心原因)
1. 标准库的强依赖(核心原因) 容器操作(如 std::vector 扩容) 当标准库容器(如 std::vector)需要重新分配内存时,它会尝试移动现有元素到新内存,而非拷贝(为了性能&…...
USB3.0走线注意事项和其中的协议
USB3.0走线的要求: 1、USB要走差分,阻抗控制为90欧姆,并包地处理,总长度最好不要超过1800mil. 2、尽可能缩短走线长度,优先考虑对高速USB差分(RX、TX差分)的布线,USB差分走线在走线…...
【Linux】iptables命令的基本使用
语法格式 iptables [-t 表名] 管理选项 [链名] [条件匹配] [-j 目标动作或跳转]注意事项 不指定表名时,默认使用 filter 表不指定链名时,默认表示该表内所有链除非设置规则链的缺省策略,否则需要指定匹配条件 设置规则内容 -A:…...
QML 菜单控件:MenuBar、MenuBarItem、Menu、MenuItem层级关系和用法
目录 引言相关阅读关于MenuBarItem核心代码1. 主菜单栏 (MenuBar.qml)2. 主页面,包含右键菜单 (MainPage.qml)3. 主界面绑定 (Main.qml)整体结构 运行效果总结工程下载 引言 在 GUI 开发中,菜单是用户交互的核心组件。QML 提供了一套灵活的菜单控件&…...
设计模式简述(十二)策略模式
策略模式 描述基本使用使用传统策略模式的缺陷以及规避方法 枚举策略描述基本使用使用 描述 定义一组策略,并将其封装起来到一个策略上下文中。 由调用者决定应该使用哪种策略,并且可以动态替换 基本使用 定义策略接口 public interface IStrategy {…...
