Советы по оптимизации регулярных выражений Java
Я новичок в регулярном выражении Java. Мы используем шаблон для сопоставления строки. Мы используем это для проверки текстового поля и отвечаем нашим требованиям. Но в сопоставлении есть проблема с производительностью.
Выкройка: ([a-zA-Z0-9]+[ ]?(([_\-][a-zA-Z0-9 ])*)?[_\-]?)+
- Текст ввода должен начинаться с a-zA-Z0-9.
- Пространство (одиночное) разрешено между словами
- "_" и "-" разрешены, но не могут быть последовательными.
Наша проблема заключается в том, что для некоторых входных строк время CPU становится высоким и вызывает зависание потоков. Также мы получаем исключения. Кто-нибудь может помочь мне оптимизировать шаблон или предложить новый шаблон для решения моей проблемы.
Exception details
============================================
Hung thread details, all the same:
[9/28/11 11:40:07:320 CDT] 00000003 ThreadMonitor W WSVR0605W: Thread "WebContainer : 26" (0000004f) has been active for 709755 mi
lliseconds and may be hung. There is/are 1 thread(s) in total in the server that may be hung.
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3938)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3801)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3801)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.match(Pattern.java:4307)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$BranchConn.match(Pattern.java:4090)
at java.util.regex.Pattern$GroupTail.match(Pattern.java:4239)
at java.util.regex.Pattern$GroupCurly.match0(Pattern.java:4006)
at java.util.regex.Pattern$GroupCurly.match(Pattern.java:3928)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Branch.match(Pattern.java:4124)
at java.util.regex.Pattern$Ques.match(Pattern.java:3703)
at java.util.regex.Pattern$Curly.match0(Pattern.java:3794)
at java.util.regex.Pattern$Curly.match(Pattern.java:3756)
at java.util.regex.Pattern$GroupHead.match(Pattern.java:4180)
at java.util.regex.Pattern$Loop.matchInit(Pattern.java:4323)
at java.util.regex.Pattern$Prolog.match(Pattern.java:4263)
at java.util.regex.Matcher.match(Matcher.java:1139)
at java.util.regex.Matcher.matches(Matcher.java:514)
Ответы
Ответ 1
Ваша проблема заключается в катастрофическом обратном отслеживании, который вы получаете, потому что у вас есть вложенные кванторы. Это начинает становиться проблемой, когда текст не соответствует требованиям из-за экспоненциально увеличивающегося числа перестановок, которые движок регулярного выражения должен пройти, чтобы объявить сбой.
([a-zA-Z0-9]+[ ]?(([_\-][a-zA-Z0-9 ])*)?[_\-]?)+
^ ^
| repetition
repetition
Восстановите регулярное выражение следующим образом:
(?i)^(?!.*(?:--|__))[A-Z0-9][\w-]*(?: [\w-]+)*$
Java, с объяснением:
boolean foundMatch = subjectString.matches(
"(?ix) # Case-insensitive, multiline regex:\n" +
"^ # Start of string\n" +
"(?! # Assert that it impossible to match\n" +
" .* # any number of characters\n" +
" (?:--|__) # followed by -- or __\n" +
") # End of lookahead assertion\n" +
"[A-Z0-9] # Match A-Z, a-z or 0-9\n" +
"[\\w-]* # Match 0 or more alnums/dash\n" +
"(?: # Match the following:\n" +
" [\\ ] # a single space\n" +
" [\\w-]+ # followed by one or more alnums or -\n" +
")* # any number of times\n" +
"$ # End of string");
Обратите внимание, что строка не должна заканчиваться пробелом в соответствии с вашими требованиями. Если вам интересно, \w
является сокращением для [A-Za-z0-9_]
.
Ответ 2
Ваше регулярное выражение допускает явление, известное как Катастрофическое откат.
Следуйте ссылке для полного описания, но вкратце у вас есть комбинация факультативного сопоставления, что означает, что оценка должна продолжаться в любой комбинации предшествующих символов, что приводит к операциям n!
(я очень уверен в n!
), который быстро ударит ваш стек.
Попробуйте это регулярное выражение:
^(?!.*(__|--| ))[a-zA-Z0-9][\w -]*(?<! )$
Пояснение:
-
^(?!.*(__|--| ))
означает, что весь вход не должен содержать 2 смежных _
или -
или пробела (лучший способ выражения "не более одного пробела между словами" - забыть о словах - проверить пробелы)
-
[a-zA-Z0-9][\w -]*
означает, что при запуске должна быть буква или номер, остальное может быть любой комбинацией букв, цифр, символов подчеркивания (\w = [a-zA-Z0-9_]
), пробелов и тире (с учетом указанных выше двух условий)
-
[^ ]$
означает, что не заканчивается пробелом (не указано, но кажется разумным) добавьте другие символы в класс символов, например -
, как вам нравится - но черта, если используется, должна быть первой или последней)
Это регулярное выражение не приведет к катастрофическому обратному отскоку.
Ответ 3
Я думаю, что это будет намного легче понять, если вы начнете с меньшей проблемы. Самый простой способ совместить несколько слов, разделенных пробелами, таков:
^\w+(?:[ ]\w+)*$
Чтобы обеспечить соблюдение правила без последовательных дефисов или символов подчеркивания, все, что вам нужно сделать, это добавить их в существующий класс символов:
(?i)^[A-Z0-9]+(?:[ _-][A-Z0-9]+)*$
Однако ваше регулярное выражение явно разрешало их также в конце строки, а мое - нет. Я буду считать, что все в порядке и делайте это так же, как и вы:
(?i)^[A-Z0-9]+(?:[ _-][A-Z0-9]+)*[_-]?$
Попробуйте последнее регулярное выражение и посмотрите, работает ли оно для вас.
EDIT: Якоря, ^
и $
, на самом деле не нужны, если вы используете метод matches()
, но они тоже не болят, и они помогают общаться ваше намерение.
Ответ 4
цифровой шаблон
^(([\\@|\\-|\\_]*([0-9]+))+)*(([\\@|\\-|\\_])+)*$
char шаблон
(([\\@|\\-|\\_]*([a-zA-Z]+))+)*(([\\@|\\-|\\_])+)*$
вводится поле ввода выше двух для условий ниже
* должен иметь как минимум одну цифру и char
* может иметь только @, _, - как специальные символы, а не другие
Ответ 5
Я хотел опубликовать это в качестве комментария к ответу Тима Пицккера, но решил, что там не будет достаточно места. В то время как регулярное выражение, данное Тимом, в какой-то степени предотвращает катастрофическое обратное отслеживание, оно все еще имеет вложенные кванторы, которые могут вызвать проблему с конкретным текстом ввода и вызвать StackOverflowException, как показано ниже. Я хочу повторить, что этот ответ является просто демонстрацией странного исключения StackOverflowException, и это не должно быть нареканием на любой ответ.
public class CatastrophicBacktrackingRegexRemedy {
public static void main(String[] args) {
regexWithNoStackOverflow();
regexCausingStackOverflow();
}
/**
* Stackoverflow is caused on a specific input,
* In this case "A " repeated about 1000 times.
* This is because of the nested quantifier (?:[\\ ][\\w-]+)*
*/
private static void regexCausingStackOverflow() {
StringBuilder subjectStringBuilder = new StringBuilder();
String subjectString = "A ";
for (int i = 0; i < 1000; i++) {
subjectStringBuilder.append(subjectString);
}
subjectStringBuilder.append("A");
//2001 character input
System.out.println("Input length :" + subjectStringBuilder.length());
//This causes stackoverflow exception on a string with 2001 characters
//on my JVM
boolean foundMatch = subjectStringBuilder.toString().matches(
"(?ix) # Case-insensitive, multiline regex:\n"
+ "^ # Start of string\n"
+ "(?! # Assert that it impossible to match\n"
+ " .* # any number of characters\n"
+ " (?:--|__) # followed by -- or __\n"
+ ") # End of lookahead assertion\n"
+ "[A-Z0-9]+ # Match A-Z, a-z or 0-9\n"
+ "(?: # Match the following:\n"
+ " [\\ ] # a single space\n"
+ " [\\w-]+ # followed by one or more alnums or -\n"
+ ")* # any number of times\n"
+ "$ # End of string");
//This will not be printed because of exception in the matches method
System.out.println("Is match found? " + foundMatch);
}
/**
* Stackoverflow can be avoided by modifying the negative lookahead
* and removing the nested quantifiers as show below.
*/
private static void regexWithNoStackOverflow(){
StringBuilder subjectStringBuilder = new StringBuilder();
String subjectString = "A ";
for (int i = 0; i < 1000000; i++) {
subjectStringBuilder.append(subjectString);
}
//returns match = true
subjectStringBuilder.append("A");
//uncomment following and it will give match = false (space in the end)
//subjectStringBuilder.append("A A ");
//uncomment following and it will give match = false (multiple spaces)
//subjectStringBuilder.append("A A");
//2000001 character input
System.out.println("Input length :" + subjectStringBuilder.length());
boolean foundMatch = subjectStringBuilder.toString().matches(
"(?ix) # Case-insensitive, multiline regex:\n"
+ "^ # Start of string\n"
+ "(?! # Assert that it impossible to match\n"
+ " .* # any number of characters\n"
+ " (?:--|__|\\s{2,}|\\s+$) # followed by -- or __ or more than a "
+ " # single space or a space in the end\n"
+ ") # End of lookahead assertion\n"
+ "[A-Z0-9]+ # Match A-Z, a-z or 0-9\n"
+ "(?: # Match the following:\n"
+ " [\\s\\w-] # followed by a space or alnum or -\n"
+ ")* # any number of times\n"
+ "$ # End of string");
System.out.println("Is match found? " + foundMatch);
}
}
Ответ 6
Вы не разместили какой-либо код, но я вижу, что вы запускаете его в веб-приложении. Думаю, оптимизация шаблона вам не поможет. Помните, что класс Matcher не является потокобезопасным.
Экземпляры этого класса небезопасны для использования несколькими параллельными темы
Попробуйте скомпилировать ваш шаблон в синхронизированном блоке и использовать метод reset() класса Matcher.