我们已经准备好了,你呢?

2024我们与您携手共赢,为您的企业形象保驾护航!

//博客/

正则表达式可以看作是一种 DSL,但它的应用极其广泛,可以很轻松的解决很多场景下的字符串匹配和过滤问题。同时有一句老话:

“如果你有一个问题并用正则表达式解决它,那么你现在就有两个问题了。”

有些人在拥有 时会想“我知道,我会用 ”。现在他们有两个 。

今天我们来聊聊Java正则表达式的问题以及它的一些优化点。

1. 问题

最近同事发现一个正则表达式在本地运行正常,但是放到集群上去就会时不时失败。

我先简化一下代码:

java.util.正则表达式。

java.util.正则表达式。

类测试{

无效的主要([]参数){

final = "([=+]|[\\s]|[\\p{P}]|[A-Za-z0-9]|[\u4E00-\u9FA5])+";

行 = 新的();

.输出。(“++++++++++++++++++++++++++++++++”);

对于 (int i = 0; i < 10; i++) {

线。(

“+.&=1&key=%%%%%%%%&=1_5”);

线。(

“”);

线。(

“”);

line.("城市&=设计费用开始低&final=1&jump=1&=gls");

线。(

“http%3A%2F%%2Fjob%2F%3Fkey%3D%25E7%25BD%2591%25E4%25B8%258A%25E5%2585%25BC%25E8%2581%258C%%3D%25E7%25BD%2591%25E4%25B8%258A%25E5%2585%25BC%25E8%2581%258C%%3D1%%3D2%%3Dgls%%%253D0%%3D4”);

线。(” \”);

p_a = 空;

尝试 {

p_a = 桩();

m_a = p_a。(线);

当(m_a.find()){

a = m_a.group();

.出。(a);

} 捕获(e){

// 去做:

.out.("线条大小: " + line.());

执行后的结果是:

++++++++++++++++++++++++++++++

在“main”java.lang 中。

在 java.util.regex.$Loop.match()

在 java.util.regex.$.match()

在 java.util.regex.$.match()

在 java.util.regex.$.match()

......

最初,这个问题是从集群抛出的。可以看到,这个异常有两个特点:

(1)由于Error直接继承自,所以无法捕获,所以就算要捕获,也要捕获Error。

(2)还有一点就是,可以看到抛出的错误并没有指定行号,当这样的代码混杂在一个几百行,几十个类似的正则表达式的工具类中时,无疑会给问题定位带来很大困难,这就需要我们具备一定的单元测试能力。

笔记:

(1)如果你的环境没有抛出上述错误,请尝试增加for循环次数或者指定jvm参数:-Xss1k

(2)如果还不明白什么意思,可以参考上一篇文章:JVM运行时数据区域简介

2.问题分析

正则表达式引擎分为两类,一类叫做 DFA(确定性有限自动机),另一类叫做 NFA(非确定性有限自动机)。这两类引擎要想顺利工作,都必须有一个正则表达式和一个文本字符串。DFA 会将正则表达式与文本字符串进行比较,当它看到一个子正则表达式时,它会标记所有可能匹配的字符串,然后查看正则表达式的下一部分,并根据新的匹配结果更新标记。而 NFA 则会将正则表达式与文本进行比较,当它吃掉一个字符时,它会将其与正则表达式进行比较,记住匹配,然后继续。一旦不匹配,它会一个接一个地吐出刚刚吃掉的字符,直到返回到最后一个匹配。

DFA与NFA机制上的差异主要有五个方面的影响:

1、DFA 只需要扫描文本字符串中每个字符一次,速度较快但特征较少。NFA 需要反复吃掉和吐出字符,速度慢但特征丰富,因此应用广泛。当今主流的正则表达式引擎,如 Perl、Ruby、re 模块、Java 和 .NET regex 库等都是 NFA。

2.仅NFA支持lazy等特性;

3.NFA急于邀功,所以优先匹配最左边的正则表达式,偶尔会错过最佳匹配结果;DFA是“优先匹配最长的左边正则表达式”。

4.NFA默认使用量词;

5. NFA可能陷入递归调用的陷阱,性能很差。

使用正则表达式时,底层是递归调用并执行的,每层递归都会占用一定数量的堆栈线程大小的内存,如果递归层数较多,则会报异常。所以使用正则表达式其实有利有弊。

在 Java 程序中,每个线程都有自己的 Stack Space,这个 Stack Space 并不是从 Heap 中分配的,所以 Stack Space 的大小不会受到 -Xmx 和 -Xms 的影响,这两个 JVM 参数只会影响 Heap 的大小。Stack Space 用于在递归调用方法时推送 Stack Frame,所以当递归调用过深时,有可能耗尽 Stack Space 而导致错误。Stack Space 的大小随 OS、JVM 和环境变量的大小而变化,一般来说默认是 512K,在 64 位系统中 Stack Space 值会更大,一般来说 128K 的 Stack Space 就足够了,这时候你需要做的就是观察,如果你的程序没有任何错误,那么可以使用 -Xss 将 Stack Space 大小调整为 128K。(eg:-)

文章开头的问题可以简单理解为方法嵌套调用层次过深,上层方法堆栈一直未释放,导致堆栈空间不足。

接下来我们要做的是了解一些常规性能的优化点,以避免这种深度递归调用。

3.Java正则表达式的一些优化点

3.1 pill()预编译表达式

如果您在程序中多次使用相同的正则表达式,请务必使用pile()对其进行编译,而不是直接使用.()。如果您在相同的正则表达式上反复使用.()(例如在循环中),则未编译的正则表达式将更加昂贵,因为.()方法将在每次使用时预编译该表达式。另外,请记住,您可以通过调用reset()方法使用不同的输入字符串重用该对象。

3.2 注意选择(的)

像“(X|Y|Z)”这样的正则表达式因会减慢程序速度而声名狼藉,因此请小心谨慎。首先,考虑选择的顺序,以便将更常用的选择放在首位,这样可以更快地匹配它们。此外,尝试提取常见模式;例如,将“(abcd|abef)”替换为“ab(cd|ef)”。后者匹配速度更快,因为 NFA 将尝试匹配 ab,如果找不到,则不会再尝试任何其他选择。(在当前情况下,只有两个选择。如果有很多选择,速度将显著提高。)选择确实会减慢程序速度。在我的测试中,表达式“.*(abcd|efgh|ijkl).*”比调用 .() 三次(表达式中的每个选择一次)慢三倍。

3.3 减少分组和嵌套

如果您实际上不需要获取组内的文本,请使用非捕获组,例如“(?:X)”而不是“(X)”。

总结一下:减少分支选择,减少捕获嵌套,减少贪婪匹配

4.解决方案

4.1 临时工计划

try...catch.../add -Xss,治标不治本,所以不推荐。

4.2 优化正则化才是出路

4.2.1 语法优化

根据3.2,我们进行如下优化:

最后 = "([=+\\s\\p{P}A-Za-z0-9\u4E00-\u9FA5])+";

经过测试,若JVM参数不变,for循环重复100万次直至OOM都不会再出现文章开头提到的栈溢出问题。

4.2.2 业务逻辑优化

由于对作者的业务场景不熟悉,做业务优化比较困难。总的原则是,当你的正则表达式过于复杂时,可以考虑拆分逻辑,或者部分不遵循正则表达式。如果你把正则表达式当成万能工具,可能就得不偿失了。

总结:在字符串查找匹配领域,正则表达式几乎是万能的,但在很多场景下,其成本也不容小觑,如何写出高效可维护的正则表达式或者如何避免使用正则表达式都是值得思考的问题。

5. NFA引擎常规性能优化技巧

1.优先选择最左边的匹配结果

2. 首先匹配标准量词

例如,使用 '.*[0-9][0-9]' 来匹配字符串“stuvw”,此时匹配方式为,'.*' 首先匹配整行,但无法满足后面两个数字的匹配,因此 '.*' 返回一个字符 'w',仍然无法匹配,继续返回一个 'v',循环返回该字符,直到找到 '2' 匹配一个,但仍然无法匹配两个数字,因此继续返回 '1'

3. 谨慎使用捕获括号 (),改用非捕获括号 (?:)

捕获括号需要一些内存

4. 使用字符组来替换分支(替换)条件

例如,使用 [ad] 代替 a|b|c|d 以避免不必要的回溯

5. 不要滥用字符组(不要将字符组用于单个字符)

\。 代替[。]

6. 使用锚点^$\b 加速定位

7.从两次中提取所需元素

a{2,4} 写为 aa{0,2}

8.提取多选结构开头的相同字符

the|this 变成 th(?:e|is)

9. 选择字符串中出现次数最多的字符串,并将其放在分支的最前面

10. 能懒就懒,不要贪心

在 * + {m, n} 后面加问号,变为非贪婪模式

摘要:引用CFC4N,滥用句号*星号+加号()括号,既不环保,又不负责任!

11. 避免使用正则表达式进行简单的字符串处理

参考:

[1]Java正则表达式引发的问题及解决方法

[2] Java正则表达式与堆栈溢出

[3] Java中正则表达式的优化

[4] 让我们从正则表达式开始

[5] 正则表达式(三):各类问题(下)

%E4%BD%99%E6%99%9F

[6] 使用 RegEx 进行大量输入时

[7] Java中在堆栈上try/catch?

[8] Java正则表达式引发无限循环问题的解决方法

[9] JAVA正则表达式溢出问题及不完全解决办法

[10] NFA引擎正则化优化技巧、Perl正则化技巧及正则化性能评估方法

[11] Java正则表达式的思考

[12] 高级正则表达式

[13] 正则表达式引发的一场谋杀

二维码
扫一扫在手机端查看

本文链接:https://by928.com/3728.html     转载请注明出处和本文链接!请遵守 《网站协议》
我们凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求,请立即点击咨询我们或拨打咨询热线: 13761152229,我们会详细为你一一解答你心中的疑难。

项目经理在线

我们已经准备好了,你呢?

2020我们与您携手共赢,为您的企业形象保驾护航!

在线客服
联系方式

热线电话

13761152229

上班时间

周一到周五

公司电话

二维码
微信
线