非捕获组是否是多余的?

9

你好!以下是关于"可选的非捕获组"是否多余的问题:

下面这个正则表达式:

(?:wo)?men

语义上等同于以下正则表达式?

(wo)?men

我认为这取决于您在哪里使用正则表达式。Java的标准正则表达式字符串可能需要它,而我相当确定Perl会认为它是多余的。 - thecoshman
5
非捕获组会占用更多的处理器资源(因为它需要额外的处理),而捕获组会占用更多的内存(因为它必须存储许多内容)。但是从语义上讲,它们可以以不同的方式匹配相同的内容,因此在某种程度上它们是等效的。你可以将它们看作拥有不同引擎的汽车,但两者都可以用于乘坐。 - Muhammad Imran
1
嗨,如果您认为下面的回答对您有帮助,请考虑接受。 - Wiktor Stribiżew
2
@MuhammadImran,你能提供一个证明非捕获组对处理器负担很重的参考吗? - Christopher Schultz
如果我的下面的答案对您有用,请考虑接受该答案。 - Wiktor Stribiżew
2个回答

12
你的(?:wo)?men(wo)?men在语义上是等价的,但从技术上来说是不同的,即前者使用的是非捕获组,而后者使用的是捕获组。因此,问题是为什么我们有了捕获组还要使用非捕获组呢?
有时候,非捕获组会有帮助。
  1. 为避免过多的反向引用(请记住,使用大于9的反向引用有时很困难)。
  2. 通过减少编号捕获组的数量来避免99个编号反向引用限制的问题(来源:Regular-expressions.info大多数正则表达式支持多达99个捕获组和双位数字反向引用。
    注意,这不适用于Java正则表达式引擎,也不适用于PHP或.NET正则表达式引擎。
  3. 我们可以添加更多分组到现有的正则表达式中,而不会破坏捕获组的顺序,以减少存储捕获结果的开销。
  4. 减少由于将捕获结果存储在堆栈中而导致的开销。

另外,它只是使我们的匹配更加清晰:

您可以使用非捕获组来保留组织或分组的好处,但不会增加捕获的开销。

将现有的正则表达式重构为非捕获组以转换捕获并不是一个好主意,因为这可能会破坏代码或需要过多的努力。


2
请注意,99个反向引用限制不适用于Java正则表达式引擎。在Java中,捕获组的数量存储在transient int capturingGroupCount中,因此理论上可以有很多反向引用,但是数量可能会受到内存限制的限制。 - Wiktor Stribiżew
我正在尝试找出我们所谈论的开销有多大,以及这实际上会产生什么影响(Java和Javascript)。 在性能方面使用非捕获组是否有任何真正的好处? - runlevel0
1
@runlevel0:我没有测量过,但看到一些评论,人们声称非捕获组有一些微小的优势。然而,在模式内部的定量组中滥用捕获组的情况下(不在最终模式位置),正则表达式引擎会努力尝试调整/重置每次回溯进入重复捕获组时捕获的值,并且如果重复捕获组匹配的文本块很大,则性能问题可能已经变得明显。 - Wiktor Stribiżew
1
查看此PHP答案。C++的std :: regex也报告了类似的问题。 - Wiktor Stribiżew
1
对于 JavaScript,非捕获组通常会有轻微的性能提升:https://jsperf.com/regex-capture-vs-non-capture - Paul Wagland

0

在另一个问题中,有人提出了相同的问题,我用Python提供了一个带有示例的答案:

它并没有"具有相同的效果"——在一个情况下,组被捕获并可访问,在另一个情况下,它仅用于完成匹配。

当人们不感兴趣访问组的值时,他们使用非捕获组——为了在出现多个匹配项的情况下节省空间,但也为了在正则表达式引擎针对此进行优化的情况下获得更好的性能。

以下是一个无用的Python示例来说明这一点:

from timeit import timeit
import re

chars = 'abcdefghij'
s = ''.join(chars[i % len(chars)] for i in range(100000))


def capturing():
    re.findall('(a(b(c(d(e(f(g(h(i(j))))))))))', s)


def noncapturing():
    re.findall('(?:a(?:b(?:c(?:d(?:e(?:f(?:g(?:h(?:i(j))))))))))', s)


print(timeit(capturing, number=1000))
print(timeit(noncapturing, number=1000))

输出:

5.8383678999998665
1.0528525999998237

注意:这是尽管PyCharm(如果您使用它)会警告"不必要的非捕获组",但警告是正确的,但不是全部事实。逻辑上是不需要的,但绝对没有完全相同的实际效果。
如果您要摆脱它们的原因是为了抑制这样的警告,PyCharm允许您这样做:
# noinspection RegExpUnnecessaryNonCapturingGroup
re.findall('(?:a(?:b(?:c(?:d(?:e(?:f(?:g(?:h(?:i(j))))))))))', s)

另外,对于学究式的读者,上面的例子也并不完全等价。但它们匹配相同的字符串,只是结果有所不同。
c = re.findall('(a(b(c(d(e(f(g(h(i(j))))))))))', s)
nc = re.findall('(?:a(?:b(?:c(?:d(?:e(?:f(?:g(?:h(?:i(j))))))))))', s)

c 是一个由 10 元组组成的列表 ([('abcdefghij', 'bcdefghij', ..), ..]),而 nc 则是由单个字符串组成的列表 (['j', ..])。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接