Для общей проблемы сопоставления текста между разделителями (например, <
и >
) есть два общих шаблона:
*
или +
в форме START [^ END] * END
, например <[^>] *>
или *?
или +?
в форме START. *? КОНЕЦ
, например <. *?>
. Есть ли особая причина отдавать предпочтение одному перед другим?
Некоторые преимущества:
[^>]*
:
/s
. [^>]
движок не делает выбора - мы даем ему только один способ сопоставить шаблон с цепочкой). .*?
(?:(?!END).)*
. Это еще хуже, если разделитель END - это другой шаблон. То, что большинство людей не учитывают при подходе к таким вопросам, - это то, что происходит, когда регулярное выражение не может найти соответствие. Это - , когда вероятнее всего появятся смертельные провалы. Например, возьмите пример Тима, где вы ищете что-то вроде <tag>Hello!
. Рассмотрим, что происходит с:
<.*?>Hello!
Механизм регулярных выражений находит <
и быстро находит закрытие >
, но не >Hello!
. Таким образом, .*?
продолжает искать >
, за которым следует , за которым следует Hello!
. Если его нет, он пройдет весь путь до конца документа, прежде чем сдастся. Затем механизм регулярных выражений возобновляет сканирование, пока не находит другой <
, и пытается снова. Мы уже знаем, как это получится, но движок регулярных выражений, как правило, этого не делает; он проходит через один и тот же ригамарол с каждым <
в документе. Теперь рассмотрим другое регулярное выражение:
<[^>]*>Hello!
Как и раньше, оно быстро соответствует от <
до >
, но не соответствует Hello!
. Он вернется к <
, затем выйдет и начнет поиск другого <
. Он по-прежнему будет проверять каждый <
, как это делал первый регулярное выражение, но не будет искать весь путь до конца документа каждый раз, когда его найдет.
.*?
фактически эквивалентен негативному прогнозу. Он говорит: «Прежде чем использовать следующий символ, убедитесь, что остаток регулярного выражения не может совпадать в этой позиции». Другими словами,
/<.*?>Hello!/
... эквивалентно:
/<(?:(?!>Hello!).)*(?:>Hello!|\z(*FAIL))/
Таким образом, на каждой позиции, которую вы выполняете, не просто обычная попытка матча, но гораздо более дорогой смотреть вперед. (Это как минимум вдвое дороже, потому что смотрящий вперед должен отсканировать хотя бы один символ, затем .
идет вперед и потребляет символ.)
((*FAIL)
- это один из обратных методов Perl . -control verbs (также поддерживается в PHP). |\z(*FAIL)
означает «или дойти до конца документа и сдаться».)
Наконец, есть еще одно преимущество подхода класса отрицанных символов , Хотя он (как указал @Bart) не действует как квантификатор собственнический, ничто не мешает вам сделать притяжательным, если ваш аромат поддерживает его:
/<[^>]*+>Hello!/
... или обернуть его в атомарную группу:
/(?><[^>]*>)Hello!/
Мало того, что эти регулярные выражения никогда не будут возвращаться без необходимости, они не должны сохранять информацию о состоянии, которая делает возможным возврат.