Советы по оптимизации регулярных выражений 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.