Почему этот шаблон занимает много времени, чтобы соответствовать в Java? - PullRequest
2 голосов
/ 21 сентября 2011

Следующий шаблон регулярных выражений при применении к очень длинным строкам (60 КБ) вызывает «зависание» Java.

.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.*

Я не понимаю, почему.

Ответы [ 3 ]

5 голосов
/ 21 сентября 2011

В этом регулярном выражении есть 5 рьяных / жадных квантификаторов, так что вы вполне могли бы сделать огромное количество возвратов.

В этой статье подробно объясняется это поведение и есть предложения поваша производительность в регулярных выражениях улучшается.

В этом случае ответ может состоять в том, чтобы заменить жадные квантификаторы не жадными, или, что еще лучше, использовать неподкатные подшаблоны.

4 голосов
/ 21 сентября 2011

По сути, «. *» (Соответствует любому количеству чего-либо) означает попытку сопоставить всю строку, если она не совпадает, затем вернуться и повторить попытку и т. Д. Использование одного из них не слишком много проблема, но время, необходимое для использования более одного, увеличивается в геометрической прогрессии. Это довольно глубокое (и гораздо более точное) обсуждение такого рода вещей: http://discovery.bmc.com/confluence/display/Configipedia/Writing+Efficient+Regex

РЕДАКТИРОВАТЬ: (я надеюсь, вы действительно хотели знать, почему)

Пример строки источника:

aaffg,  ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks

ОДИН СПОСОБ СМОТРЕТЬ НА ЭТО:

Процесс занимает так много времени, потому что .* соответствует всей исходной строке (aaffg, ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks), только чтобы обнаружить, что он не заканчивается символом |, а затем возвращается к последнему случаю | символ (...4876|2345...), затем пытается найти следующий .* до конца строки.

Он начинает поиск следующего | символа, указанного в вашем выражении, и не находит его, затем возвращается к первому сопоставленному символу | (указанному в ...4876|2345...), отбрасывает это совпадение и находит ближайший | перед ним (...dfas|2936...), чтобы он мог соответствовать второму символу | в вашем выражении соответствия.

Затем он будет сопоставлять .* с 2936827634876, а второй | - с одним из ...4876|2345..., а следующий .* - с оставшимся текстом, только чтобы найти, что вы хотели еще один |. Затем он будет продолжать возвращаться снова и снова, пока не совпадет со всеми символами, которые вы указали.

ДРУГОЙ СПОСОБ СМОТРЕТЬ НА ЭТО:

(Исходное выражение):

.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.*

это примерно переводится на

match:
               any number of anything, 
followed by    a single '|', 
followed by    any number of anything, 
followed by    a single '|', 
followed by    any number of anything, 
followed by    a single '|', 
followed by    any number of anything,
followed by    the literal string 'bundle',
followed by    any number of anything

проблема в том, что any number of anything включает | символов, требующих многократного разбора всей строки, где вы на самом деле имеете в виду any number of anything that is not a '|'

Чтобы исправить или улучшить выражение, я бы порекомендовал три вещи:

Сначала (и наиболее значимый) , замените большинство "совпадений с чем угодно" (.*) классами отрицанных символов ([^|]), например так:

[^|]*\Q|\E[^|]*\Q|\E[^|]*\Q|\E.*bundle.*

... это будет препятствовать тому, чтобы оно совпадало с концом строки снова и снова, но вместо этого сопоставляло бы все не | символы до первого символа, который не является "не | символом "(это двойное отрицание означает до первого | символа), затем соответствует символу |, затем идет к следующему и т. д ...

Второе изменение (несколько значимое, в зависимости от исходной строки) должно приводить к тому, что второе-последнее "соответствует любому количеству чего-либо" (.*) в "ленивый" или " неохотно "типа" любое число "(.*?). Это заставит его пытаться сопоставить что-либо с идеей поиска bundle вместо пропуска bundle и сопоставления остальной части строки, только чтобы понять, что есть еще что-то, чтобы соответствовать, как только он туда добирается, вынужденный откатываться назад , Это приведет к:

[^|]*\Q|\E[^|]*\Q|\E[^|]*\Q|\E.*?bundle.*

Третье изменение Я бы порекомендовал для удобочитаемости - замените блоки \Q\E одиночным экранированием, как в \|, например:

[^|]*\|[^|]*\|[^|]*\|[^|].*?bundle.*

Это - то, как выражение всегда обрабатывается внутренне - есть буквально функция, которая преобразует выражение, чтобы "экранировать все специальные символы между \ Q и \ E" - \Q\E только сокращение, и если это делает не делайте ваше выражение короче или проще для чтения, его не следует использовать. Период.

Классы отрицанных символов имеют неотключенный |, потому что | не является специальным символом в контексте классов символов - но давайте не будем слишком отвлекаться. Вы можете избежать их, если хотите, но не обязаны.

Окончательное выражение примерно переводится на:

match:
               any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything that is not a '|', 
followed by    a single '|', 
followed by    any number of anything, up until the next expression can be matched,
followed by    the literal string 'bundle',
followed by    any number of anything

Хороший инструмент, который я использую (но стоит немного денег), называется RegexBuddy - бесплатный / бесплатный веб-сайт для понимания регулярных выражений http://www.regular -expressions.info , а конкретная страница, объясняющая повторение, - http://www.regular -expressions.info / repeat.html

RegexBuddy эмулирует другие движки регулярных выражений и говорит, что вашему исходному регулярному выражению потребовалось бы 544 «шага» для сравнения, а не 35 «шагов» для предоставленной мной версии.

SLIGHTLY LONGER Пример исходной строки A:

aaffg,  ";;p[p09978|ksjfsadfas|12936827634876|2345.4564a bundle of sticks

SLIGHTLY LONGER Пример исходной строки B:

aaffg,  ";;p[p09978|ksjfsadfas|2936827634876|2345.4564a bundle of sticks4me

Более длинная исходная строка 'A' (добавлено 1 до 2936827634876) не повлияло на предложенную мной замену, но увеличило оригинална 6 шагов

Более длинная исходная строка 'B' (добавленная '4me' в конце выражения) снова не повлияла на предложенную мной замену, но добавила 48 шагов к исходному

Таким образом, в зависимости от того, чем строка отличается от приведенных выше примеров, строка из 60 КБ может выполнить всего 544 шага или более миллиона шагов

4 голосов
/ 21 сентября 2011

Во-первых, я думаю, вы могли бы упростить свое регулярное выражение следующим образом:

.*\Q|\E.*\Q|\E.*\Q|\E.*bundle.*

может стать

.*\|.*\|.*\|.*bundle.*

Во-вторых, чтобы ответить на ваш вопрос, тот факт, что в вашем регулярном выражении так много символов ". *", Означает, что существует множество возможностей, через которые решение для регулярных выражений должно работать. Следующее может работать быстрее, если это подойдет для вашей ситуации.

(?:[^\|]*\|){3}[^|]*bundle.*

изменяя ". " на "[^ |] ", вы сужаете фокус двигателя, так как первый ". *" Не будет охотно захватывать первый "|".

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...