PHP Conference Japan 2024

仅一次子模式

对于最大化和最小化重复,后续内容的失败通常会导致重新评估重复项,以查看不同的重复次数是否允许模式的其余部分匹配。有时,这样做很有用,无论是为了改变匹配的性质,还是为了使其比其他情况下更早失败,当模式的作者知道继续下去毫无意义时。

例如,考虑将模式 \d+foo 应用于主题行 123456bar

在匹配所有 6 位数字然后未能匹配“foo”后,匹配器的正常操作是再次尝试仅匹配 5 位数字的 \d+ 项,然后是 4 位数字,依此类推,最终失败。仅一次子模式提供了一种方法来指定一旦模式的一部分匹配,就不会以这种方式重新评估它,因此匹配器在第一次未能匹配“foo”时会立即放弃。符号是另一种特殊括号,以 (?> 开头,例如在此示例中: (?>\d+)bar

这种类型的括号在匹配后会“锁定”它包含的模式部分,并且模式中进一步的失败将无法回溯到其中。但是,回溯到它之前的项目仍然照常工作。

另一种描述是,这种类型的子模式匹配与相同独立模式匹配的字符字符串,如果该模式锚定在主题字符串中的当前位置。

仅一次子模式不是捕获子模式。上述示例等简单情况可以认为是必须吞噬所有内容的最大化重复。因此,虽然 \d+ 和 \d+? 都准备调整它们匹配的数字数量以使模式的其余部分匹配,但 (?>\d+) 只能匹配整个数字序列。

当然,此构造可以包含任意复杂的子模式,并且可以嵌套。

仅一次子模式可以与后顾断言结合使用,以指定主题字符串末尾的高效匹配。考虑一个简单的模式,例如 abcd$ 当应用于不匹配的长字符串时。因为匹配从左到右进行,所以 PCRE 将在主题中查找每个“a”,然后查看后续内容是否与模式的其余部分匹配。如果模式指定为 ^.*abcd$ 则初始 .* 首先匹配整个字符串,但当此操作失败(因为没有后续的“a”)时,它会回溯以匹配除最后一个字符以外的所有字符,然后匹配除最后两个字符以外的所有字符,依此类推。再次搜索“a”涵盖整个字符串,从右到左,因此我们并没有变得更好。但是,如果模式写成 ^(?>.*)(?<=abcd) 则 .* 项无法回溯;它只能匹配整个字符串。随后的后顾断言对最后四个字符进行单次测试。如果失败,则匹配立即失败。对于长字符串,这种方法对处理时间有显着影响。

当模式在一个本身可以无限次重复的子模式内包含无限次重复时,使用仅一次子模式是避免某些失败匹配花费很长时间的唯一方法。模式 (\D+|<\d+>)*[!?] 匹配无限数量的子字符串,这些子字符串要么由非数字组成,要么由括在 <> 中的数字组成,后跟 ! 或 ?。当它匹配时,它运行很快。但是,如果将其应用于 aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa 它需要很长时间才能报告失败。这是因为字符串可以在两种重复之间以多种方式划分,并且必须尝试所有这些方式。(使用的示例是 [!?] 而不是结尾处的单个字符,因为 PCRE 和 Perl 都有一种优化,可以在使用单个字符时允许快速失败。它们记住匹配所需的最后一个单个字符,如果字符串中不存在该字符,则会尽早失败。)如果模式更改为 ((?>\D+)|<\d+>)*[!?] 非数字序列不能被破坏,并且失败很快发生。

添加注释

用户贡献注释 1 条注释

匿名
2 年前
不要将“仅一次子模式”(?>...)放在“单行”注释(#、//)中。
我花了将近 1 天的时间才修复它。
改用'C'风格的注释!

PHP 手册说,“单行”注释(#、//)在“?>”之前结束(PHP 结束标记)。

“单行”注释(#、//)中的这些字母“?>”似乎被评估为 PHP 结束标记。

<?php

/* (?> */
echo '"C" style comment works !<br>';

# (?>
echo '"one-line" comment';

?>
To Top