使用正则表达式时-可能会导致性能下降的情况

目录

前言

正则表达式引擎

NFA自动机的回溯

解决方案


  • 前言

  • 正则表达式是一个用正则符号写出的公式,程序对这个公式进行语法分析,建立一个语法分析树,再根据这个分析树结合正则表达式的引擎生成执行程序(这个执行程序我们把它称作状态机,也叫状态自动机),用于字符匹配
  • 使用不恰当的正则表达式可能会导致很严重的性能问题
  • 比如:

  • 这个正则表达式看起来没什么问题,它可以分为三个部分:
  • 第一部分匹配 http 和 https 协议,第二部分匹配 www. 字符,第三部分匹配许多字符
  • 其实这里会导致 CPU 使用率高的关键原因就是:Java 正则表达式使用的引擎实现是 NFA 自动机,这种正则表达式引擎在进行字符匹配时会发生回溯backtracking
  • 而一旦发生回溯,那其消耗的时间就会变得很长,有可能是几分钟,也有可能是几个小时,时间长短取决于回溯的次数和复杂度
  • 正则表达式引擎

  • 正则表达式引擎就是一套核心算法,用于建立状态机
  • 简单地说,实现正则表达式引擎的有两种方式:DFA 自动机(Deterministic Final Automata 确定型有穷自动机)和 NFA 自动机(Non deterministic Finite Automaton 不确定型有穷自动机)
  • 简单来讲,NFA 对应的是正则表达式主导的匹配,而 DFA 对应的是文本主导的匹配
  • 简单来讲,DFA 自动机的时间复杂度是线性的,更加稳定,但是功能有限;而 NFA 的时间复杂度比较不稳定,有时候很好,有时候不怎么好,好不好取决于你写的正则表达式;但是胜在 NFA 的功能更加强大,所以包括 Java、.NET、Perl、Python、Ruby、PHP 等语言都使用了 NFA 去实现其正则表达式
  • 那 NFA 自动机到底是怎么进行匹配的呢?我们以下面的字符和表达式来举例说明:

  • 要记住一个很重要的点,即:NFA 是以正则表达式为基准去匹配的
  • 也就是说,NFA 自动机会读取正则表达式的一个一个字符,然后拿去和目标字符串匹配,匹配成功就换正则表达式的下一个字符,否则继续和目标字符串的下一个字符比较
    • 首先,拿到正则表达式的第一个匹配符:d
    • 于是拿去和字符串的字符进行比较,字符串的第一个字符是T,不匹配,换下一个
    • 第二个是o,也不匹配,再换下一个
    • 第三个是d,匹配了,那么就读取正则表达式的第二个字符:a
    • 读取到正则表达式的第二个匹配符:a
    • 拿着继续和字符串的第四个字符 a 比较,又匹配了
    • 那么接着读取正则表达式的第三个字符:y
    • 读取到正则表达式的第三个匹配符:y
    • 拿着继续和字符串的第五个字符 y 比较,又匹配了
    • 尝试读取正则表达式的下一个字符,发现没有了,那么匹配结束
  • 上面这个匹配过程就是 NFA 自动机的匹配过程,但实际上的匹配过程会比这个复杂非常多,但其原理是不变的
  • NFA自动机的回溯

  • 了解了 NFA 是如何进行字符串匹配的,接下来我们就可以讲讲这篇文章的重点了:回溯
  • 为了更好地解释回溯,我们同样以下面的例子来讲解:

  • 上面的这个例子的目的比较简单,匹配以 a 开头,以 c 结尾,中间有 1-3 个 b 字符的字符串
  • NFA 对其解析的过程是这样子的:
    • 首先,读取正则表达式第一个匹配符 a 和 字符串第一个字符 a 比较,匹配了
    • 于是读取正则表达式第二个字符
    • 读取正则表达式第二个匹配符 b{1,3} 和字符串的第二个字符 b 比较,匹配了
    • 但因为 b{1,3} 表示 1-3 个 b 字符串,以及 NFA 自动机的贪婪特性(也就是说要尽可能多地匹配),所以此时并不会再去读取下一个正则表达式的匹配符,而是依旧使用 b{1,3} 和字符串的第三个字符 b 比较,发现还是匹配
    • 于是继续使用 b{1,3} 和字符串的第四个字符 c 比较,发现不匹配了
    • 此时就会发生回溯
    • 发生回溯是怎么操作呢?
    • 发生回溯后,我们已经读取的字符串第四个字符 c 将被吐出去,指针回到第三个字符串的位置
    • 之后,程序读取正则表达式的下一个操作符 c,读取当前指针的下一个字符 c 进行对比,发现匹配
    • 于是读取下一个操作符,但这里已经结束了
  • 下面我们回过头来看看前面的那个校验 URL 的正则表达式:

  • 出现问题的 URL 是:

  • 我们把这个正则表达式分为三个部分:
    • 第一部分:校验协议;^([hH][tT]{2}[pP]://|[hH][tT]{2}[pP][sS]://)
    • 第二部分:校验域名;(([A-Za-z0-9-~]+).)+
    • 第三部分:校验参数;([A-Za-z0-9-~\\/])+$
  • 我们可以发现正则表达式校验协议 http:// 这部分是没有问题的,但是在校验 www.fapiao.com 的时候,其使用了 xxxx. 这种方式去校验
  • 那么匹配过程是这样的:
    • 匹配到 www.
    • 匹配到 fapiao.
    • 匹配到 com/dzfp-web/pdf/download?request=6e7JGm38jf.....,你会发现因为贪婪匹配的原因,所以程序会一直读后面的字符串进行匹配,最后发现没有点号,于是就一个个字符回溯回去了
  • 这是这个正则表达式存在的第一个问题
  • 另外一个问题是在正则表达式的第三部分,我们发现出现问题的 URL 是有下划线(_)和百分号(%)的,但是对应第三部分的正则表达式里面却没有
  • 这样就会导致前面匹配了一长串的字符之后,发现不匹配,最后回溯回去
  • 这是这个正则表达式存在的第二个问题
  • 解决方案

  • 明白了回溯是导致问题的原因之后,其实就是减少这种回溯,你会发现如果我在第三部分加上下划线和百分号之后,程序就正常了

  • 运行上面的程序,立刻就会打印出match!!
  • 但这是不够的,如果以后还有其他 URL 包含了乱七八糟的字符呢,我们难不成还再修改一遍
  • 肯定不现实
  • 其实在正则表达式中有这么三种模式:贪婪模式、懒惰模式、独占模式
  • 在关于数量的匹配中,有 + ? * {min,max} 四种两次,如果只是单独使用,那么它们就是贪婪模式
  • 如果在他们之后多加一个 ? 符号,那么原先的贪婪模式就会变成懒惰模式,即尽可能少地匹配
  • 但是懒惰模式还是会发生回溯现象
  • 例如下面这个例子:

  • 正则表达式的第一个操作符 a 与 字符串第一个字符 a 匹配,匹配成功
  • 于是正则表达式的第二个操作符 b{1,3}? 和 字符串第二个字符 b 匹配,匹配成功
  • 因为最小匹配原则,所以拿正则表达式第三个操作符 c 与字符串第三个字符 b 匹配,发现不匹配
  • 于是回溯回去,拿正则表达式第二个操作符 b{1,3}? 和字符串第三个字符 b 匹配,匹配成功
  • 于是再拿正则表达式第三个操作符 c 与字符串第四个字符 c 匹配,匹配成功;于是结束
  • 如果在他们之后多加一个 + 符号,那么原先的贪婪模式就会变成独占模式,即尽可能多地匹配,但是不回溯
  • 于是乎,如果要彻底解决问题,就要在保证功能的同时确保不发生回溯
  • 将上面校验 URL 的正则表达式的第二部分后面加多了个 + 号,即变成这样:

  • 这样之后,运行原有的程序就没有问题了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/210130.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C语言--每日选择题--Day35

第一题 1. 有如下定义:(x y) % 2 (int) a / (int) b 的值是() int x 3; int y 2;float a 2.5; float b 3.5; A:0 B:2 C:1.5 D:1 答案及解析 D 本题是考查强制类型转换和操作符优先级 操作…

idea连接mysql详细讲解

IDEA连接mysql又报错!Server returns invalid timezone. Go to Advanced tab and set serverTimezone prope 前进的道路充满荆棘。 错误界面 IDEA连接mysql,地址,用户名,密码,数据库名,全都配置好了&…

常见的几种计算机编码格式

前言: 计算机编码是指将字符、数字和符号等信息转换为计算机可识别的二进制数的过程,正因如此,计算机才能识别中英文等各类字符。计算机中有多种编码格式用于表示和存储文本、字符和数据,实际走到最后都是二进制,本质一…

Android Edittext进阶版(Textfieids)

一、Text fieids 允许用户在 UI 中输入文本,TextInputLayout TextInputEditText。 在 Text fieids 没出来(我不知道)前,想实现这个功能就需要自己自定义控件来实现这个功能。 几年前做个上面这种样式(filled 填充型)。需要多个控件组合 动画才能实现&a…

【单片机】MCU内存管理

keil中查看内存使用情况 Code-Data,RO-Data,RW-Data,ZI-Data的含义 Code-Data:代码占用的flash大小 RO-Data:[read-only data],只读常量大小(const和#define) RW-Data:[read write data],初始化了的变量大小 ZI-Da…

uniapp如何与原生应用进行混合开发?

目录 前言 1.集成Uniapp 2.与原生应用进行通信 3.实现原生功能 4.使用原生UI组件 结论: 前言 随着移动应用市场的不断发展,使用原生开发的应用已经不能满足用户的需求,而混合开发成为了越来越流行的选择。其中,Uniapp作为一种跨平台的开…

PyQt6 QToolButton工具按钮控件

​锋哥原创的PyQt6视频教程: 2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili2024版 PyQt6 Python桌面开发 视频教程(无废话版) 玩命更新中~共计32条视频,包括:2024版 PyQt6 Python桌面开发 视频教程(无废话…

WPF halcon 机器视觉

1 鼹鼠的故事第14集 鼹鼠与智能房 鼹鼠无意中坐进了一辆小汽车,小汽车开进了一所智能住宅。鼹鼠看到房主在智能房里,享受着现代化的服务。趁着主人看电视的时候,鼹鼠也享用了一顿丰盛的智能晚餐。 小编大胆的畅想,这些食物 前一秒…

创建conan包-Understanding Packaging

创建conan包-Understanding Packaging 1 Understanding Packaging1.1 Creating and Testing Packages Manually1.2 Package Creation Process 本文是基于对conan官方文档Understanding Packaging翻译而来, 更详细的信息可以去查阅conan官方文档。 1 Understanding …

Docker Image(镜像)——5

目录: Docker 镜像是什么镜像生活案例镜像分层生活案例为什么需要镜像镜像命令详解 镜像命令清单docker imagesdocker tagdocker pulldocker pushdocker rmidocker savedocker loaddocker historydocker importdocker image prunedocker build镜像操作案例 查找镜像…

微信小程序 老年人心血管健康知识科普系统

本系统的功能有管理员:个人中心,用户管理,热点信息管理,疾病管理,疾病类型管理,治疗管理,治疗类型管理,护理管理,护理类型管理,科普管理,科普类型…

HTTP 缓存机制

一、强制缓存 只要浏览器判断缓存没有过期,则直接使用浏览器的本地缓存而无需再请求服务器。 强制缓存是利用下面这两个 HTTP 响应头部(Response Header)字段实现的,它们都用来表示资源在客户端缓存的有效期: Cache…

力扣572:另一棵树的子树

力扣572:另一棵树的子树 给你两棵二叉树 root 和 subRoot 。检验 root 中是否包含和 subRoot 具有相同结构和节点值的子树。如果存在,返回 true ;否则,返回 false 。 二叉树 tree 的一棵子树包括 tree 的某个节点和这个节点的所…

nodejs微信小程序+python+PHP就业求职招聘信息平台的设计与实现-计算机毕业设计推荐

目 录 摘 要 I ABSTRACT II 目 录 II 第1章 绪论 1 1.1背景及意义 1 1.2 国内外研究概况 1 1.3 研究的内容 1 第2章 相关技术 3 2.1 nodejs简介 4 2.2 express框架介绍 6 2.4 MySQL数据库 4 第3章 系统分析 5 3.1 需求分析 5 3.2 系统可行性分析 5 3.2.1技术可行性:…

导入JDBC元数据到Apache Atlas

前言 前期实现了导入MySQL元数据到Apache Atlas, 由于是初步版本,且功能参照Atlas Hive Hook,实现的不够完美 本期对功能进行改进,实现了导入多种关系型数据库元数据到Apache Atlas 数据库schema与catalog 按照SQL标准的解释,…

全文检索[ES系列] - 第495篇

历史文章(文章累计490) 《国内最全的Spring Boot系列之一》 《国内最全的Spring Boot系列之二》 《国内最全的Spring Boot系列之三》 《国内最全的Spring Boot系列之四》 《国内最全的Spring Boot系列之五》 《国内最全的Spring Boot系列之六》 M…

java设计模式学习之【建造者模式】

文章目录 引言建造者模式简介定义与用途实现方式: 使用场景优势与劣势建造者模式在spring中的应用CD(光盘)的模拟示例UML 订单系统的模拟示例UML 代码地址 引言 建造者模式在创建复杂对象时展现出其强大的能力,特别是当这些对象需…

linux 应用开发笔记---【标准I/O库/文件属性及目录】

一,什么是标准I/O库 标准c库当中用于文件I/O操作相关的一套库函数,实用标准I/O需要包含头文件 二,文件I/O和标准I/O之间的区别 1.标准I/O是库函数,而文件I/O是系统调用 2.标准I/O是对文件I/O的封装 3.标准I/O相对于文件I/O具有更…

为什么 SQL 不适合图数据库

背景 “为什么你们的图形产品不支持 SQL 或类似 SQL 的查询语言?” 过去,我们的一些客户经常问这个问题,但随着时间的推移,这个问题变得越来越少。 尽管一度被忽视,但图数据库拥有无缝设计并适应其底层数据结构的查询…

SpringBoot启动流程

SpringBoot启动流程 文章目录 SpringBoot启动流程SpringBoot启动流程 SpringBoot启动流程 视频链接: https://www.bilibili.com/video/BV15b4y1a7yG/?p174&spm_id_frompageDriver&vd_sourcef6debc5a79e3f424f9dde2f13891b158 看李老师讲的吧,特…